-
无法使用调试工具从远程附加进程
-
快速恢复为先,即使在结婚,也得赶紧修复线上问题。而且生产环境流量大、网络权限严格、调用链路复杂,因此更容易出问题,也是出问题最多的环境。
===================================================================
生产环境出现问题时,因为要尽快恢复应用,就不可能保留完整现场用于排查和测试。因此,是否有充足的信息(日志、监控和快照)可以了解历史、还原bug 场景。
最常用的就是 ELK 的日志了,注意:
-
确保错误、异常信息可被完整记录到文件日志
-
确保生产上程序的日志级别是INFO以上
记录日志要使用合理的日志优先级,DEBUG用于开发调试、INFO用于重要流程信息、WARN用于需要关注的问题、ERROR用于阻断流程的错误
生产环境需开发配合运维才能做好完备监控:
对CPU、内存、磁盘、网络等资源做监控。如果应用部署在虚拟机或k8s集群,那么除了对物理机做基础资源监控外,同样还要对虚拟机或Pod监控。监控层数取决于应用的部署方案,有一层OS就要做一层监控。
监控专线带宽、交换机基本情况、网络延迟
不仅仅是监控进程对CPU、内存、磁盘IO、网络使用的基本指标,更重要的是监控组件内部的一些重要指标。比如最常用的Prometheus,就提供了大量exporter对接各种中间件和存储系统
需监控JVM进程的类加载、内存、GC、线程等常见指标(比如使用Micrometer来做应用监控),此外还要确保能够收集、保存应用日志、GC日志
我们再来看看快照。这里的“快照”是指,应用进程在某一时刻的快照。通常情况下,我们会为生产环境的Java应用设置-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath=…这2个JVM参数,用于在出现OOM时保留堆快照。这个课程中,我们也多次使用MAT工具来分析堆快照。
==========================================================================
定位问题,首先要定位问题出在哪个层次:Java应用程序自身问题还是外部因素导致。
-
可以先查看程序是否有异常,异常信息一般比较具体,可以马上定位到大概的问题方向
-
如果是一些资源消耗型的问题可能不会有异常,我们可以通过指标监控配合显性问题点来定位。
一般问题原因可归类如下:
回滚,再慢慢通过版本差异分析根因。
比如主机、中间件或DB问题。
这种按主机层面问题、中间件或存储(统称组件)的问题分为:
主机层
可使用工具排查:
CPU相关
使用top、vmstat、pidstat、ps
内存相关
使用free、top、ps、vmstat、cachestat、sar
IO相关
使用lsof、iostat、pidstat、sar、iotop、df、du
网络相关
使用ifconfig、ip、nslookup、dig、ping、tcpdump、iptables
组件
从如下方面排查:
-
组件所在主机是否有问题
-
组件进程基本情况,观察各种监控指标
-
组件的日志输出,特别是错误日志
-
进入组件控制台,使用一些命令查看其运作情况。
通常先通过重启和扩容解决问题,之后再分析,最好能留个快照。
系统资源不够,一般可能:
CPU使用高
若现场还在,具体分析流程:
- 在服务器执行
top -Hp pid
查看进程中哪个线程CPU使用高
-
输入大写的P将线程按照 CPU 使用率排序,并把明显占用CPU的线程ID转换为16进制
-
在jstack命令输出的线程栈中搜索这个线程ID,定位出问题的线程当时的调用栈
若无法直接在服务器执行top,可采样定位:间隔固定时间运行一次jstack,采样几次后,对比采样得出哪些线程始终处于运行状态,找出问题线程。
若现场没了,可排除法分析。CPU使用高,一般是由下面的因素引起的:
- 突发压力
可通过应用之前的负载均衡的流量或日志量确认,诸如Nginx等反向代理都会记录URL,可依靠代理的Access Log进行细化定位,也可通过监控观察JVM线程数的情况。压力问题导致CPU使用高的情况下,如果程序的各资源使用没有明显不正常,之后可以通过压测+Profiler(jvisualvm就有这个功能)进一步定位热点方法;如果资源使用不正常,比如产生了几千个线程,就需要考虑调参
- GC
可通过JVM监控GC相关指标、GC Log确认。如果确认是GC压力,那么内存使用也很可能会不正常,需要按照内存问题分析流程做进步分析。
- 死循环或不正常处理流程
可以结合应用日志分析。一般情况下,应用执行过程中都会产生一些日志,可以重点关注日志量异常部分。
内存泄露或OOM
最简单的就是堆转储后使用MAT分析。堆转储,包含了堆现场全貌和线程栈信息,一般观察支配树图、直方图就可以马上看到占用大量内存的对象,可以快速定位到内存相关问题
Java进程对内存的使用不仅仅是堆区,还包括线程使用的内存(线程个数*每一个线程的线程栈)和元数据区。每一个内存区都可能产生OOM,可以结合监控观察线程数、已加载类数量等指标分析
注意看JVM参数的设置是否有明显不合理的,限制了资源。
IO问题
除非是代码问题引起的资源不释放等问题,否则通常都不是由Java进程内部因素引发的。
网络
一般也是由外部因素引起。对于连通性问题,结合异常信息通常比较容易定位;对于性能或瞬断问题,可以先尝试使用ping等工具简单判断,如果不行再使用tcpdump或Wireshark。
=======================================================================
偶尔可能分析和定位难题,会迷失自我。如果你也这样,可参考如下经验
比如业务执行的很慢,而且线程数增多,那就可能是:
- 代码逻辑有问题、依赖的外部服务慢
使得自己的业务逻辑执行缓慢,在访问量不变情况下,就需要更多线程处理。比如,10 TPS的并发原先一次请求1s即可完成,10个线程可支撑;现在执行完成需要10s,就需100个线程
- 请求量增大
使得线程数增多,应用本身CPU不足,上下文切换问题导致处理变慢
这时就需要多结合监控指标和各服务的入口流量,分析慢是cause or result。
如果没头绪,那就试试总结规律吧!
比如
-
有一堆服务器做负载均衡,出问题时可分析监控和日志看请求是否是均匀分布的,可能问题都集中在某个机器节点上
-
应用日志一般会记录线程名称,出问题时可分析日志是否集中在某类线程
-
若发现应用开启大量TCP连接,通过netstat可分析出主要集中连接到哪个服务
探求到了规律,就很容易突破了。
比如看到Nginx返回502,一般可认为是下游服务的问题导致网关无法完成请求转发。
对于下游服务,不能想当然就认为是我们的Java程序,比如在拓扑上可能Nginx代理的是Kubernetes的Traefik Ingress,链路是Nginx->Traefik->应用,如果一味排查Java程序的健康,则始终找不到根因。
有时虽然使用了Feign进行服务调用,出现连接超时也不一定就是服务端问题,有可能是客户端通过URL调用服务端,并非通过Eureka的服务发现实现的客户端负载均衡。即客户端连接的是Nginx代理而非直接连接应用,客户端连接服务出现的超时,其实是Nginx代理宕机所致。
观察各种监控指标,如果发现曲线慢慢上升然后稳定在一个水平线,一般就是资源达到瓶颈。
观察网络带宽曲线时,如果带宽上升到120MB左右不动了,很可能就是打满了1GB的网卡或传输带宽
观察到数据库活跃连接数上升到10个不动了,很可能是连接池打满了
观察监控一旦看到任何这样曲线,都要引起重视。
CPU、内存、IO和网络相辅相成,一个资源出现瓶颈,很可能同时引起其他资源连锁反应。
内存泄露后对象无法回收会造成大量Full GC,CPU会大量消耗在GC从而引起CPU使用增加
经常会把数据缓存在内存队列进行异步IO,网络或磁盘出现问题时,就很可能会引起内存暴涨。
所以出问题时,要综合考虑避免误判
比如MySQL访问慢了,可能:
-
客户端原因,连接池不够导致连接获取慢、GC停顿、CPU占满
-
传输过程问题
包括光纤可能被挖断了呀、防火墙、路由表等设置有问题
- 真的服务端背锅了
这都需要逐一排查区分。
服务端慢一般可以看到MySQL出慢日志,传输慢一般可以通过ping来简单定位,排除了这两个可能,并且仅仅是部分客户端出现访问慢的情况,就需要怀疑是客户端本身的问题。对于第三方系统、服务或存储访问出现慢的情况,不能完全假设是服务端的问题。
第七,快照类工具和趋势类工具需要结合使用。比如,jstat、top、各种监控曲线是趋势类工具,可以让我们观察各个指标的变化情况,定位大概的问题点;而jstack和分析堆快照的MAT是快照类工具,用于详细分析某一时刻应用程序某一个点的细节。
一般情况下,我们会先使用趋势类工具来总结规律,再使用快照类工具来分析问题。如果反过来可能就会误判,因为快照类工具反映的只是一个瞬间程序的情况,不能仅仅通过分析单一快照得出结论,如果缺少趋势类工具的帮助,那至少也要提取多个快照来对比。