MQ队列堆积问题定位
原创2021/2/9...大约 3 分钟
问题现象
生产环境Rabbit MQ集群监控告警,部分队列消息堆积。
排查思路和步骤
登录Rabbit MQ集群管理界面,查看队列消息堆积情况,以及查看是否存在Consumer对消息进行消费。通过管理端查到队列中堆积了30w+消息,有7个Consumer进程但消费速度在3-5条/s。怀疑消费线程存在死锁阻塞了,或是由于性能太差导致消费缓慢。
紧急重启消费者的半数实例,堆积的消息被重新消费,系统恢复正常,剩余未重启的实例用于问题排查
使用
jstat -gcutil查看gc的情况,可以看到gc并不算特别频繁,应该不是gc导致的性能问题
使用
jstack -l命令dump出线程状态,通过线程名(在代码中预先设置好)查找MQ的消费线程,发现不存在消费线程,也就是说所有消费线程均已终止运行,由于消费线程是通过线程池来管理,怀疑是线程池状态异常为查看线程池状态,使用
jmap -dump命令dump出堆快照,使用MAT工具分析,结合代码找到线程池对象,其状态如下图所示,可以看到线程池已经处于TERMINATED状态。

- 如果线程池已关闭,则必然调用过
shutdown()或shutdownNow(),通过跟踪代码发现是MQ的框架存在bug,当管理端配置变更后通知到客户端,需要客户端刷新配置,这里的实现是先终止所有的MQ消费,再尝试重新加载配置初始化,bug在于初始化时并没有再次拉起线程池。至此问题定位完毕。
问题解决
- 应急方案:在MQ管理端配置更新后,需要手动重启应用
- 优化MQ框架代码,改为在更新配置时不关闭线程池,需要对客户端进行升级
总结
- 给线程命名是一个好习惯,在排查问题时可通过线程名来快速定位问题
- 排查问题过程中可能会需要查看一个对象的内部状态,通过堆dump当然可以实现,但是dump需要先执行一次full gc,可能会影响应用性能。可以考虑在生产环境安装arthas等工具,或将关键对象暴露到MBean中。
线程池状态
线程池中的ctl是一个int类型变量(32bit),其前3位表示线程池状态,后29位表示有效执行的Worker数量。
| 线程池状态 | ctl前3位 | 含义 |
|---|---|---|
| RUNNING | 100 | 运行中,可以正常接受提交新任务 |
| SHUTDOWN | 000 | 不接受新提交的任务,但可以处理队列中的任务 |
| STOP | 001 | 不接受新提交的任务,不处理队列中的任务,并中断正在执行的任务 |
| TIDYING | 010 | 所有的任务都已终止,workerCount置为0,即将调用terminated()钩子函数 |
| TERMINATED | 011 | 执行完terminated() |
简单来说,如果ctl<0表示线程池正常运行,否则正处于关闭过程中或已关闭。