BlackChen'site

我是如何排查生产问题的

生产问题排查基本命令

当线上服务出现问题的时候, 可以通过固定的步骤, 获取线上环境的信息, 一步一步逐步定位问题所在.

X00. 判断问题影响范围

当突然接到线上报警, 应立即判断问题影响范围,如果直接导致服务不可用,则需立即响应(包括重启服务,或进行服务迁移扩容,正常情况下靠谱的运维不会让这个情况发生).

如果是高可用部署, 则联系运维同事, 切换流量到另外几台正常的机器(修改NGINX配置等),保留作案现场,进行分析定位问题.

X01. 查看cpu

  • top

    • 查看cpu详细信息: 按1
    • 查看负载
    • 按cpu使用率排序: 按P
    • 查看COMMAND详细信息: 按c
  • top -H -p pid

    • 查看某进程下的线程信息

X02. 查看内存

  • top
    • 按内存排序: 按M
  • free -h

X03. 查看磁盘

  • df -h

X04. 定位进程号

方法1 通过ps命令

例如: 服务名称为fcrm-c-rest
命令: ps -ef | grep 'fcrm-c-rest' 则可以查询到名称为fcrm-c-rest的进程号
ps-ef

方法2 通过top命令

敲击top命令后, 输入MP 分别根据内存使用量排序, 和CPU使用量排序来进行定位线程号
top

方法3 通过jps命令

通过jps 命令获取当前执行的java进程

jps - Lists the instrumented Java Virtual Machines (JVMs) on the target system. This command is experimental and unsupported.

X05. 查看线程堆栈

  • jstack pid
  • 使用arthas 中的thread指令

X06. 查看堆信息

  • jmap -heap pid
  • arthas 中的 jvm指令
  • jmap -histo:live pid 查看存活对象信息

X07. 查看GC

  • jstat -gcutil pid 1s 每1秒打印一次gc信息
  • arthas 中的 jvm指令

实战经历

以下项目均为救火....

线上服务器磁盘空间占满,导致文件上传失败问题

背景: 旧项目因客户机房运维资源不足, 机器维护不及时磁盘空间占满.

问题现象: 突然客户反映列表导出异常, 无法正常导出数据.

排查:

  1. 登录线上服务器, 查看cpu资源, 正常
  2. 查看异常日志信息,发现某时刻后的所有日志都没有保留
  3. 查看磁盘信息, 发现磁盘空间已满. 定位问题.

解决:

  1. 删除无用文件, 扩容磁盘
  2. 排查日志打印方式. 减少无用日志输出.

复盘:

  1. 客户服务器磁盘空间太小-> 只有80G
  2. 缺少完善的监控体系
  3. 日志配置有问题,可以设置日志存留时间
  4. 缺少定时打包日志

线上数据库表缺少索引, 导致更新时,锁全表,数据库连接占盘,tomcat线程打满,拒绝请求

背景: 发送微信模板消息后, 微信有发送回调. 可以处理模板消息发送情况.

问题现象: 客户发送模板消息, 20万条, 服务立马拒绝各种请求, 但是模板消息正常发送.微信登录等C端请求全部失效.

排查:
1. 登录线上服务器, 查看cpu资源, 正常.
2. 查看内存信息, 正常
3. 日志一直报错
2020-05-26 11:00:58 [ERROR]-[http-nio-62550-exec-172]-[F93D2D5C-A37D-4267-B0A9-097324554882]-[com.mklinfo.rest.handler.RestExceptionHandler.handleException(RestExceptionHandler.java:75)] GlobalExceptionHandler handleException error org.apache.catalina.connector.ClientAbortException: java.io.IOException: Broken pipe at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:321) at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:284) at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:118) at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:297) at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141) at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229) at org.springframework.util.StreamUtils.copy(StreamUtils.java:119) at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:106) at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:41) at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:228) at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:247) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:174) at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:113) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:849) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:760) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970) at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872) at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846) at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)

怀疑是文件服务数被打满, 使用arthasjvm指令查看文件打开数, 发现略多但也属于正常范围
4. 使用arthasthread指令, 发现tomcathttp-nio-62550-exec-XX线程被打满, 默认200个
5. 使用 jstack 命令, 发现很多线程阻塞在一个地方,查看代码发现阻塞在update,都在等待druid数据库连接
6. 联系DBA查看数据库情况, 发现锁表. 表中没有索引.
7. 问题原因: 因发送模板消息, 数据量大, 微信回调更新状态, 表中无索引,导致锁表,更新速度慢, 导致Tomcat线程被占满, 拒绝请求.

解决:
1. 增加数据库索引
2. 增加数据库连接数量
复盘:
1. 根本原因是数据库没有索引导致
2. druid 连接池线程数设置太小: 初始线程10,最大20 -> 调整为初始20,最大50

总结

  1. 注意jmap -dump:format=b,file=filename pid命令,转储堆文件,线上不要轻易使用, 在大内存情况下,会导致服务无法提供服务
  2. 推荐使用[arhtas](https://github.com/alibaba/arthas/blob/master/README_CN.md)

评论