前言 {#前言}
最近工作中线上环境发现几个进程 CPU 异常飙高,在应用层面排查了很久也没有发现问题,那只能抓堆栈来看看具体的情况了。
开始排查 {#开始排查}
-
top 查看 cpu 飙高的进程获取 pid
-
top -H -p pid 查看进程中线程的 cpu 占用大小
-
jstack pid > xxx
-
pid 转 16 进制,在 xxx 文件中进行查询异常的堆栈
找到了 CPU 飙高的异常堆栈:
"MultiStageCoprocessor-other-canalxn66j7f4a2w_INCREMENT-14-thd-1" #55 prio=5 os_prio=0 tid=0x00007fafdc0bd000 nid=0x2aa0 runnable [0x00007fafe4ce6000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000078063a388> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
at com.lmax.disruptor.TimeoutBlockingWaitStrategy.waitFor(TimeoutBlockingWaitStrategy.java:38)
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.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
排查结论 {#排查结论}
观察上面的堆栈,很明显的发现使用了 Disruptor 高性能队列框架,而使用的等待策略是 TimeoutBlockingWaitStrategy;TimeoutBlockingWaitStrategy 是一种等待策略,用于在使用 Disruptor 消息队列时,阻止消费者线程在等待新消息时忙等待 CPU 资源。
这种等待策略通过使用 Java 的 LockSupport 类,阻塞消费者线程一段时间,然后检查是否有新的消息可用。如果没有新的消息可用,它会重复该过程,直到有新的消息到来。
如果发现 TimeoutBlockingWaitStrategy 导致 CPU 飙高,可能是因为设置了等待时间过短,这会导致消费者线程频繁地阻塞和唤醒,从而导致 CPU 负载高。
解决这个问题的方法是尝试增加等待时间,这样消费者线程将有更多的时间来等待新的消息,而不会频繁地被阻塞和唤醒。另外还可以考虑使用其他的等待策略,如 SleepingWaitStrategy 或 BusySpinWaitStrategy,这些等待策略可能更适合,也可以帮助减少 CPU 的使用率。
相关总结 {#相关总结}
| 等待策略 | 简要说明 | |---------------------------------|-----------------------------------------------------------------------------------------------------------------------| | BlockingWaitStrategy | 通过线程阻塞的方式,等待生产者唤醒,被唤醒后,再循环检查依赖的 sequence 是否已经消费 | | BusySpinWaitStrategy | 线程一直自旋等待,可能比较耗 cpu | | LiteBlockingWaitStrategy | 线程阻塞等待生产者唤醒,与 BlockingWaitStrategy 相比,区别在 signalNeeded.getAndSet,如果两个线程同时访问一个访问 waitfor,一个访问 signalAll 时,可以减少lock加锁次数 | | LiteTimeoutBlockingWaitStrategy | 与 LiteBlockingWaitStrategy 相比,设置了阻塞时间,超过时间后抛异常 | | PhasedBackoffWaitStrategy | 根据时间参数和传入的等待策略来决定使用哪种等待策略 | | TimeoutBlockingWaitStrategy | 相对于 BlockingWaitStrategy 来说,设置了等待时间,超过后抛异常 | | YieldingWaitStrategy | 尝试100次,然后 Thread.yield() 让出cpu |