alibaba / jetcache

JetCache is a Java cache framework.
Apache License 2.0
5.15k stars 1.06k forks source link

线上死锁 #914

Open vibodse opened 3 months ago

vibodse commented 3 months ago

版本

2.7.5

场景

这是线上的一个死锁场景,服务使用的xxl-job跑的定时任务,任务中调用了jetcache注解缓存的方法,跑一段时间定时任务就都停了,查看线程发现是死锁了,具体原因不清楚,因为我技术一般,大佬如果有空帮忙可以看一眼。 死锁截图

areyouok commented 3 months ago

这不是死锁,只是慢,你是不是本地缓存太多了。还有那个缓存失效通知,默认是关闭的,无脑打开只会死得快

areyouok commented 3 months ago

我看了下这里代码没有问题,性能可以略微优化,我稍后改改,但解决不了你的本质问题

areyouok commented 3 months ago

本地cache多可以用CaffeineCache

vibodse commented 3 months ago

本地cache多可以用CaffeineCache

好的,感谢您的回复,我尝试优化一下。

HetaoWangl commented 2 months ago

本地cache多可以用CaffeineCache

我也遇到了一样的问题,应该是死锁了,运行一段时间后,服务的句柄数就会无限增长,因为通过linkedHashMap获取缓存值时,无法获取到锁,所以所有的请求都会阻塞,最终随着时间会出现无数个阻塞的线程,通过jstack查看,是LinkedHashMapCache.cleanExpiredEntry现成一直没有释放写锁资源。我的版本是2.7.6,补充一下,我的缓存数据量不多,大概只有10-20个之间,并且map的大小设置了100,jdk是21 jstack1 jstack2

areyouok commented 2 months ago

第一个图表示正在clean占据了锁,第二个图表示正在等clean完成(LinkedHashMapCache.java:94),如果没有第三个图揭示在等什么别的东西,那就还是map里面东西太多导致clean太慢了。

之前的程序在Cleaner类里面加了一把大锁,这里已经优化还未发布,不过和你给出的两个图无关。

另外,线程A(或多个线程)等线程B释放锁,而线程B执行较慢迟迟不释放,这不叫死锁。线程A持有锁1,线程B持有锁2,线程A等待锁2,线程B等待锁1,永远互相锁住,这才是死锁。

areyouok commented 2 months ago

这里或许还可以优化下,但是local cache太多的话还是不好哈,毕竟LinkedHashMapCache只是自己做的一个简单的local cache

areyouok commented 2 months ago

我猜到一个可能的原因,或许和这个提交有关,2.7.5和2.7.6会受到影响,你可以把版本改为2.7.4试试。 修复这个问题的新版本2.7.7最晚明天发布。

https://github.com/alibaba/jetcache/pull/820

HetaoWangl commented 2 months ago

第一个图表示正在clean占据了锁,第二个图表示正在等clean完成(LinkedHashMapCache.java:94),如果没有第三个图揭示在等什么别的东西,那就还是map里面东西太多导致clean太慢了。

之前的程序在Cleaner类里面加了一把大锁,这里已经优化还未发布,不过和你给出的两个图无关。

另外,线程A(或多个线程)等线程B释放锁,而线程B执行较慢迟迟不释放,这不叫死锁。线程A持有锁1,线程B持有锁2,线程A等待锁2,线程B等待锁1,永远互相锁住,这才是死锁。

image 感谢回复😄。我是通过jstack的线程持续时长简单分析判断clean线程在LinkedHashMapCache的迭代过程中可能存在类似死循环的问题(或者entry太多,遍历时间久,但是目前来看概率不大),导致锁无法释放,从而造成其他读线程无法获取到读锁,描述成死锁确实有问题。我测试的环境有5个节点,15左右的qps,本地缓存过期时间设置的2H,一天以内会复现,某一个节点会开始阻塞,因为我得恢复节点,所以没法观察最长会阻塞多久,以及会不会恢复,目前最长持续过12H+,之后通过重启强制中断了,整个过程其他节点正常运行。目前已经换成了caffeine,暂时平稳。

areyouok commented 2 months ago

问题是这样的,为了更好的适应java21,在这个修改里面把synchronized换成了ReentrantLock。进一步他使用了ReentrantReadWriteLock,本意是为了提升一点点性能。但之前他没注意,我也遗漏了,其实LinkedHashMap的读操作也是带有更新的,所以就可能会导致并发问题。

https://github.com/alibaba/jetcache/pull/820

我本想把这里重新写一下,但还是从简吧,马上就发布。

HetaoWangl commented 2 months ago

问题是这样的,为了更好的适应java21,在这个修改里面把synchronized换成了ReentrantLock。进一步他使用了ReentrantReadWriteLock,本意是为了提升一点点性能。但之前他没注意,我也遗漏了,其实LinkedHashMap的读操作也是带有更新的,所以就可能会导致并发问题。

820

我本想把这里重新写一下,但还是从简吧,马上就发布。

感谢师兄😄