681 words
3 minutes
一次nacos变更引发的死锁
背景
生产环境有很多xxljob的定时任务,大概每1-2周会触发一次xxljob的任务失败。触发时机也很不固定,有时是早上,有时是下午,而且表现是对应的后续的任务全部都没有执行,需要重启整个jvm实例。
问题排查
问题怀疑:
- 一开始怀疑是网络不稳定,但是其他服务没有这个现状,只有这个服务对应的实例会卡住,所以可能性很低。
- 后续怀疑是xxljob执行器线程满了,但是查看代码,每次xxljob回调执行器,执行器会new一个线程池,不会存在线程池满导致排队的情况
- 第三点是怀疑是http请求超时,但是超时怎么会导致其他任务也卡住在某个点
- 第四个点,有个公共的业务上的锁,锁住了,但是翻看业务代码,都不存在
问题定位
在一次偶然的机会,在改动nacos变更之后,发现几十分钟之后一堆xxljob任务都卡住了。刚好用命令jstak 把线程dump出来
定位到问题:
所有xxljob线程阻塞在一个地方,就是例子中的B和C之前,然后还有一个nacos线程,他们都锁住了同一个对象。
代码类似
@Service@RefreshScopeclass A{
@Xxljob("b") public B(){
}
@Xxljob("c") public C(){
} ...}所以原因是xxljob有个耗时一个多小时的任务刚好在改nacos之前执行,然后RefreshScope导致需要刷新整个类,但是由于重新加载bean需要获取读写锁,但是正在执行的任务还有一个多小时才能执行,所以任务一直获取读锁没有释放,nacos变更之后需要获取写锁需要等这个任务执行完。那为什么后续任务都卡住呢?因为读写锁为了防止写锁饥饿,所以写锁插队,但是写锁是需要等这个正在执行的读锁释放。
最后形成了:
正在执行的任务: 还有一个多小时才能执行完
nacos更新线程: 需要更新bean(因为更新nacos配置刷新bean),但是写锁需要等正在执行的读锁释放
其他调动线程: 由于读写锁,为了防止饥饿,其他任务需要持有读锁,但是读锁需要让位写锁,所以在队列等待写锁执行。
最后形成了看到的场景:所有任务的卡住了
说到底: 锁粒度太大,如果任务不用执行一个多小时,那可能没什么问题,因为也不是死锁
一次nacos变更引发的死锁
https://tatamagic.com/posts/deadlock/