跳到主要内容

线上 CPU 异常飙高问题排查

前言

最近工作中线上环境发现几个进程 CPU 异常飙高,在应用层面排查了很久也没有发现问题,那只能抓堆栈来看看具体的情况了。

top_cmd

开始排查

  • 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