前言
突然收到线上服务cpu达到100%的报警短信,于是立即展开排查。
排查过程
理论步骤
一、找到最耗CPU的进程
工具:top
方法:
执行top -c ,显示进程运行信息列表
键入P (大写p),进程按照CPU使用率排序
二:找到最耗CPU的线程
工具:top
方法:
top -Hp PID,显示一个进程的线程运行信息列表
键入P (大写p),线程按照CPU使用率排序
三:将线程PID转化为16进制
工具:printf
之所以要转化为16进制,是因为堆栈里,线程id是用16进制表示的。
四:查看堆栈,找到线程在干嘛
工具:pstack/jstack/grep
打印进程堆栈 通过线程id,过滤得到线程堆栈
实战操作
零,发现服务器上安装的不知一个Java版本,于是首先确定下,通过如下 命令。
[root@service-dev ~]# find / -name java
/etc/java
/etc/pki/java
/etc/pki/ca-trust/extracted/java
/etc/alternatives/java
/var/lib/alternatives/java
/usr/share/java
/usr/bin/java
/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64/jre/bin/java
/usr/lib/java
一,找到耗CPU进程PID
通过top -c命令,可以发现进程PID 29812消耗的CPU明显是最多
二,找出耗CPU的线程
可以利用ps命令
[root@service-dev ~/consume-service]# ps -mp 29812 -o THREAD,tid,time
USER %CPU PRI SCNT WCHAN USER SYSTEM TID TIME
root 96.3 - - - - - - 00:01:04
root 0.0 19 - futex_ - - 29812 00:00:00
root 9.2 19 - futex_ - - 29813 00:00:06
root 1.2 19 - futex_ - - 29814 00:00:00
root 0.0 19 - futex_ - - 29815 00:00:00
root 0.0 19 - futex_ - - 29816 00:00:00
root 0.0 19 - futex_ - - 29817 00:00:00
root 12.5 19 - futex_ - - 29818 00:00:08
root 3.5 19 - futex_ - - 29819 00:00:02
root 0.0 19 - futex_ - - 29820 00:00:00
root 0.0 19 - futex_ - - 29821 00:00:00
root 0.0 19 - futex_ - - 29833 00:00:00
root 0.0 19 - futex_ - - 29834 00:00:00
root 0.0 19 - futex_ - - 29836 00:00:00
root 87.9 19 - - - - 29840 00:00:44
root 0.0 19 - ep_pol - - 29841 00:00:00
root 0.0 19 - ep_pol - - 29842 00:00:00
root 0.0 19 - ep_pol - - 29843 00:00:00
可以明显发现线程29840占用的CPU最多。
当然,也可以通过top -Hp pid命令,将这个进程的线程显示出来。输入大写的 P 可以将线程按照 CPU 使用比例排序。
三,将10进制线程ID转换为16进制,便于后续在堆栈grep
[root@service-dev ~/consume-service]# printf "%x\n" 29840
7490
四、打印堆栈并grep除目标线程
[root@service-dev ~/consume-service]# jstack 29812 | grep ‘0x7490’ -C5 --color
"disruptor-Disruptor Main--1" #14 prio=5 os_prio=0 tid=0x00007f9038952800 nid=0x7490 runnable [0x00007f9011ad4000]
java.lang.Thread.State: RUNNABLE
at java.lang.Thread.yield(Native Method)
at com.lmax.disruptor.YieldingWaitStrategy.applyWaitMethod(YieldingWaitStrategy.java:58)
at com.lmax.disruptor.YieldingWaitStrategy.waitFor(YieldingWaitStrategy.java:40)
at com.lmax.disruptor.ProcessingSequenceBarrier.waitFor(ProcessingSequenceBarrier.java:56)
at com.lmax.disruptor.BatchEventProcessor.processEvents(BatchEventProcessor.java:159)
at com.lmax.disruptor.BatchEventProcessor.run(BatchEventProcessor.java:125)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
明显,可以发现是服务使用的Disruptor组件在执行YieldingWaitStrategy策略的时候,导致了CPU升高,排查的方向就很聚焦了。
四、进入业务排查Disruptor组件使用配置
1, 简单分析
业务层面:因为这个这本身是一个消费服务,消费来自RocketMQ的消息,但是稍显不合理的地方是一个Disruptor消费者处理了多个Topic业务,鉴于最近短时业务比较多,长时业务变少,所以yield的频率明显升高。
Disruptor层面:目前采用的时YieldingWaitStrategy策略,该策略是一种充分利用CPU 的策略,使用自旋 + yield的方式来提高性能。当消费线程(Event Handler threads)的数量小于 CPU 核心数时推荐使用该策略。我这边业务上同时启动的消费者线程数量明显多于4核个数,不太合理。
2,着手处理
短期处理:修改WaitStrategy策略,降低对CPU的使用。
- BlockingWaitStrategy:(也是默认的策略):使用的是锁的机制,其对CPU的消耗最小并且在各种不同的部署环境中能提供更加一致性的表现。
- SleepingWaitStrategy:在多次循环尝试不成功后,选择让出CPU,等待下次调度,多次调度后仍不成功,尝试前睡眠一个纳秒级别的时间再尝试,这种策略平衡了延迟和CPU资源占用,但延迟不均匀。
- YieldingWaitStrategy:无所高性能策略,在多次循环尝试不成功后,选择让出CPU,等待下次调。平衡了延迟和CPU资源占用,但延迟也比较均匀。官方对 YieldingWaitStrategy 的描述里谈道:
当消费线程(Event Handler threads)的数量小于 CPU 核心数时推荐使用该策略。
所以,暂时调整为低消耗CPU的BlockingWaitStrategy,重新部署后解决了CPU升高问题,对业务的延迟影响也较小。
长远处理:
将我们现有的业务拆分,根据不同的业务需要采用不同的消费策略。
参考
https://www.imooc.com/article/259253
https://www.cnblogs.com/lishijia/p/5549980.html 100%分析
https://blog.csdn.net/jiangzhexi/article/details/77429671 100%
https://www.cnblogs.com/crossoverJie/p/10129072.html 100%分析 Disruptor
https://crossoverjie.top/2018/08/29/java-senior/OOM-Disruptor/ Disruptor的坑
https://www.cnblogs.com/lyhero11/p/5127054.html
https://fastthread.io/