背景
生产环境下某个数据同步的job执行时,该应用服务CPU居高不下,触发告警。由于是部署在一台实体高性能物理机上,该机器上还有部署其他应用服务,如果该服务CPU持续冲高,很有可能会影响到其他应用服务。如果影响到其他服务,导致大批量服务挂掉的话,年终奖也会同时挂掉。急需解决!!!
分析
该job是一个手动触发的任务,触发周期为半个月左右,需要手动触发。
任务是处理一批源数据,处理好的数据会做为下游服务的一些资源,在每次下游服务重启时去拉取最新资源并加载为缓存用于提升资源查询性能。
之前该job运行了一年多,都是稳定的。
最近一次才让CPU冲高,但该job代码最近无更改。
由于该job源数据来源多,数据杂,相互依赖多,很难在本地去模拟生产环境的数据状态。
解决
尝试复现
在预生产环境去复现,数据状态最接近生产环境,而且不会对生产环境产生影响。
先 top 查看CPU占用最高的进程pid 。
然后ps -ef | grep 确认是否是自己的服务 。
如果确定该pid是自己的服务,然后使用如下命令查看java哪个线程cpu占用高 top -H -p 。
CPU到底在干嘛
重点关注下PID,S,%CPU,%MEM。
其中,S进程状态。 D=不可中断的睡眠状态 R=运行 S=睡眠 T=跟踪/停止 Z=僵尸进程
看到有6个线程处于运行状态,cpu几乎跑满了。
然后随意取一个pid,定义为tid,如207605。
然后确定线程id,再通过如下命令计算十六进制值:printf "%x\n" ,假设输出为32af5 然后,再使用如下命令,打印该线程堆栈100条内容 jstack | grep 32af5 -A 100 如jstack 196788 | grep 32af5 -A 100
多次重复执行上述步骤,分析结果。
定位问题
经诊断发现,占用CPU高的线程堆栈始终在同一行业务代码上。
定位到具体的业务代码,是一段判断双向链表是否有环的逻辑。bug的问题在于用Set去重的过程中没有add遍历过的元素,导致死循环。
拓展
1,CPU居高不下的原因有很多,除了像上述业务代码死循环之外,还有频繁GC等。
2,如果服务部署上公有云,做成容器化,可以通过部署隔离虚拟化分配合理的CPU核数和内存大小的方法,来提升容灾水平(如果该服务消耗资源殆尽,不会影响部署在同一虚拟机上的其他容器服务)