到了年末果真都不太平,最近又收到了运维报警:表示有些服务器负载很是高,让咱们定位问题。java
还真是想什么来什么,前些天还故意把某些服务器的负载提升(没错,老板让我写个 BUG!),不过还好是不一样的环境互相没有影响。git
拿到问题后首先去服务器上看了看,发现运行的只有咱们的 Java 应用。因而先用 ps
命令拿到了应用的 PID
。github
接着使用 top -Hp pid
将这个进程的线程显示出来。输入大写的 P 能够将线程按照 CPU 使用比例排序,因而获得如下结果。服务器
果真某些线程的 CPU 使用率很是高。运维
为了方便定位问题我立马使用 jstack pid > pid.log
将线程栈 dump
到日志文件中。函数
我在上面 100% 的线程中随机选了一个 pid=194283
转换为 16 进制(2f6eb)后在线程快照中查询:性能
由于线程快照中线程 ID 都是16进制存放。
发现这是 Disruptor
的一个堆栈,前段时间正好解决过一个因为 Disruptor 队列引发的一次 [OOM]():强如 Disruptor 也发生内存溢出?优化
没想到又来一出。spa
为了更加直观的查看线程的状态信息,我将快照信息上传到专门分析的平台上。线程
其中有一项菜单展现了全部消耗 CPU 的线程,我仔细看了下发现几乎都是和上面的堆栈同样。
也就是说都是 Disruptor
队列的堆栈,同时都在执行 java.lang.Thread.yield
函数。
众所周知 yield
函数会让当前线程让出 CPU
资源,再让其余线程来竞争。
根据刚才的线程快照发现处于 RUNNABLE
状态而且都在执行 yield
函数的线程大概有 30几个。
所以初步判断为大量线程执行 yield
函数以后互相竞争致使 CPU 使用率增高,而经过对堆栈发现是和使用 Disruptor
有关。
然后我查看了代码,发现是根据每个业务场景在内部都会使用 2 个 Disruptor
队列来解耦。
假设如今有 7 个业务类型,那就等因而建立 2*7=14
个 Disruptor
队列,同时每一个队列有一个消费者,也就是总共有 14 个消费者(生产环境更多)。
同时发现配置的消费等待策略为 YieldingWaitStrategy
这种等待策略确实会执行 yield 来让出 CPU。
代码以下:
初步看来和这个等待策略有很大的关系。
为了验证,我在本地建立了 15 个 Disruptor
队列同时结合监控观察 CPU 的使用状况。
建立了 15 个 Disruptor
队列,同时每一个队列都用线程池来往 Disruptor队列
里面发送 100W 条数据。
消费程序仅仅只是打印一下。
跑了一段时间发现 CPU 使用率确实很高。
同时 dump
线程发现和生产的现象也是一致的:消费线程都处于 RUNNABLE
状态,同时都在执行 yield
。
经过查询 Disruptor
官方文档发现:
YieldingWaitStrategy 是一种充分压榨 CPU 的策略,使用自旋 + yield
的方式来提升性能。
当消费线程(Event Handler threads)的数量小于 CPU 核心数时推荐使用该策略。
同时查阅到其余的等待策略 BlockingWaitStrategy
(也是默认的策略),它使用的是锁的机制,对 CPU 的使用率不高。
因而在和以前一样的条件下将等待策略换为 BlockingWaitStrategy
。
和刚才的 CPU 对比会发现到后面使用率的会有明显的下降;同时 dump 线程后会发现大部分线程都处于 waiting 状态。
看样子将等待策略换为 BlockingWaitStrategy
能够减缓 CPU 的使用,
但留意到官方对 YieldingWaitStrategy
的描述里谈道:
当消费线程(Event Handler threads)的数量小于 CPU 核心数时推荐使用该策略。
而现有的使用场景很明显消费线程数已经大大的超过了核心 CPU 数了,由于个人使用方式是一个 Disruptor
队列一个消费者,因此我将队列调整为只有 1 个再试试(策略依然是 YieldingWaitStrategy
)。
跑了一分钟,发现 CPU 的使用率一直都比较平稳并且不高。
因此排查到此能够有一个结论了,想要根本解决这个问题须要将咱们现有的业务拆分;如今是一个应用里同时处理了 N 个业务,每一个业务都会使用好几个 Disruptor
队列。
因为是在一台服务器上运行,因此 CPU 资源都是共享的,这就会致使 CPU 的使用率居高不下。
因此咱们的调整方式以下:
BlockingWaitStrategy
,能够有效下降 CPU 的使用率(业务上也还能接受)。Disruptor
队列),一个应用处理一种业务类型;而后分别单独部署,这样也能够互相隔离互不影响。固然还有其余的一些优化,由于这也是一个老系统了,此次 dump 线程竟然发现建立了 800+ 的线程。
建立线程池的方式也是核心线程数、最大线程数是同样的,致使一些空闲的线程也得不到回收;这样会有不少无心义的资源消耗。
因此也会结合业务将建立线程池的方式调整一下,将线程数降下来,尽可能的物尽其用。
本文的演示代码已上传至 GitHub:
https://github.com/crossoverJie/JCSprout
你的点赞与分享是对我最大的支持