记一次线上Spring和Dubbo死锁排查
问题
前不久线上发现有系统间数据没有同步,排查一通下来发现应该是 MQ 消息没有被消费,通过 MQ Console 发现,未被消费的消息全都集中的一台机器上(消息投放的queue 以及 rebalance 关系),此时我有两个怀疑,一是消费者线程挂了,二是此服务分配到的消息队列出了什么莫名的问题。由于正值业务高峰,领导第一时间重启了服务,重启后一切恢复正常。排查由于一些其它事项,也到此中断。
然而没过多久,我听到其它需求项目组在测试环境出现了相同的问题,也是消息不消费了,导致业务异常。我立马找到对应服务 dump 了线程。
我将 dump 文件通过 visualVM
打开,然后得到了很明显的提示:Found one Java-level deadlock
:
1 | "ConsumeMessageThread_1": |
这里有 3 个线程:
“ConsumeMessageThread_1” MQ 消费者线程
“main”: 主线程
“Thread-26”: XXL-JOB 执行线程
其实主要是这两个线程引发的问题:
“main”: 主线程
“Thread-26”: XXL-JOB 执行线程
MQ 消费者线程是正好撞在枪口上了。
涉及两个锁:
DefaultModuleDeployer:object monitor lock
DefaultSingletonBeanRegistry.singletonObjects:object monitor lock
暂且将第一个称为 Dubbo 锁,第二个称为 Spring 锁。
“main”: 主线程做的事情是初始化一个基础服务(Dubbo Consumer)注册到 Spring 和 Dubbo 中, 而其先获取到了 Spring 锁,再去获取 Dubbo 锁。
“Thread-26”:job 线程是由 xxljob 触发后执行一个任务,需要调用一个远程的 Dubbo 服务,于是 先获取了 Dubbo 锁,再去获取 Spring 锁。
于是两个线程就互相锁死了,而 MQ 消费者线程,也要去Bean Container 中获取 Bean,也需要获取 Spring 锁,也就卡死了。
解决方案
这个问题由于是在启动过程中发生,所以我设想了两个解决方案(没有好的契机去解决这个问题,只能等到有相关需求了),其实思路是同一个,那就是将出问题的 Bean 给延迟加载:
- 将“基础服务”Bean 给@Lazy。
- 将“XxlJobSpringExecutor”在spring启动完成之后再注册进容器,避免启动过程中收到任务的执行命令。