alibaba / jetcache

JetCache is a Java cache framework.
Apache License 2.0
4.96k stars 1.03k forks source link

请求一下,使用LinkedHashMap的方法存储本地缓存不失效,并且在使用清除本地缓存方法中,写锁没有释放 #880

Closed Charles5563 closed 3 months ago

Charles5563 commented 3 months ago

在清除本地缓存方法中,方法如下: image 在这个h.getExpireTime()若是在redis当数据库使用数据永久有效的情况下,这个值为: expireAfterWriteInMillis = CacheConsts.DEFAULT_EXPIRE 1000L;//即Integer.MAX_VALUE 1000L 几乎不存在本地缓存失效的情况. 具体跟踪代码如下: image

上面是本地缓存永久有效的逻辑,所以在这种场景下,是不需要清除本地缓存的. 但是在这个场景下,实际应用过程中,却发现这个写锁存在不释放的情况,导致读数据处于等待状态,使线程堆积导致服务异常. 服务日志如下: "http-nio-8080-exec-200" #1680 daemon prio=5 os_prio=0 tid=0x00007f6f8c0c3800 nid=0x6b9 waiting on condition [0x00007f6ce9bd8000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method)

从应用程序的dump文件中,写锁的引用情况如下,可看出这个清除本地缓存的写锁没有释放,读锁排队

企业微信截图_17132539925879

请问各位大佬们,这个问题是怎么回事,怎么解决?

areyouok commented 3 months ago

你应该是本地缓存太多了,导致运行clean(加锁)的时间很长。

你一是缓存永久有效,二来估计没有指定localLimit,第三也不指定localExpire,jvm内存要爆了吧

Charles5563 commented 3 months ago

你应该是本地缓存太多了,导致运行clean(加锁)的时间很长。

你一是缓存永久有效,二来估计没有指定localLimit,第三也不指定localExpire,jvm内存要爆了吧

非常感谢作者大神的回应. 下面是我的jetcache的配置,指定了本地的limit,数据量并不大

jetcache:
    areaInCacheName: false
    local:
        default:
            expireAfterAccessInMillis: 300000
            expireAfterWriteInMillis: 600000
            keyConvertor: fastjson2
            limit: 100
            type: linkedhashmap
    remote:
        default:
            broadcastChannel: item-domain-query-service
            keyConvertor: fastjson2
            #mode: cluster
            type: redis.lettuce
            #异步获取结果的超时时间
            asyncResultTimeoutInMillis: 6000
            uri: redis://xxx
            valueDecoder: kryo5
            valueEncoder: kryo5
    statIntervalMinutes: 1

跟踪程序发现本地缓存的配置如下图: image 并且跟踪了崩溃前的jvm内存信息,没有发现任何异常 另外,从dump文件的对象集总数来看,对象占用空间不大 image

若是本地limit有效,也就是只有100个本地缓存数据,运行cleanExpiredEntry时间应该不会过长 监控这个清理动作是1分钟一次,这个运行时长超过一分钟,是否会导致写锁一直被占用? 从实际来看,应该不存在这种情形,请问还会存在哪些场景导致这个写锁没有释放呢? 谢谢.

另外补充信息: 当出现服务异常的时候,通过监控网关请求,发现请求量较少,不存在大并发的情况.

areyouok commented 3 months ago

LRUMap是个非常简单的类,里面都是内存操作,你可以自己看。

从你的上面给的图来看,LRUMap占用18%应该是不正常的,这应该不是limit 100,你可以看看别的cache。yml里面指定的limit是可以被具体的cache覆盖的。

Charles5563 commented 3 months ago

LRUMap是个非常简单的类,里面都是内存操作,你可以自己看。

从你的上面给的图来看,LRUMap占用18%应该是不正常的,这应该不是limit 100,你可以看看别的cache。yml里面指定的limit是可以被具体的cache覆盖的。

您好,感谢. 我们通过本地调试,确认这个本地缓存的数量是100,占用18%存储空间是因为单个存储对象数据量较大导致. 另外再补充服务异常时线程情况,确认是这个cleanExpiredEntry一直没有释放写锁 image 再补充一下监控cleanExpiredEntry方法的耗时情况(数据和测试环境一致): image 该方法耗时几乎为0,不存在耗时长 image

这个写锁不释放的情况不好复现,并且一直也没有找到原因,且是在测试环境中时不时(有时一周出现一次,有时一两天就出现一次)出现导致服务不可用(线程资源占满,健康检查请求通不过),而生产环境暂时未出现过.比较两个环境的配置没有差异.比较担心生产环境是否会出现该问题,所以这几天都在排查,最后搜集到这些线索,依然找不到问题的根源.跟踪代码链路都没发现任何问题.

现在想到的解决办法是绕过这个cleanExpiredEntry,使用caffeine而不使用linkedhashmap,改动一下配置即可. 但是依然很想知道这个点出在哪个地方,是怎么回事.

请大佬跟踪一下.

areyouok commented 3 months ago

LRUMap里面的锁是private的,只有它自己在用,所有的地方都是try  finally释放的,不可能会没有释放。而且try里面都是内存操作没有堵塞。

我想到的可能:测试环境是不是谁打断点了。要不是内存占用太多,cleanExpiredEntry的时候gc卡了?

Charles5563 commented 3 months ago

LRUMap里面的锁是private的,只有它自己在用,所有的地方都是try  finally释放的,不可能会没有释放。而且try里面都是内存操作没有堵塞。

我想到的可能:测试环境是不是谁打断点了。要不是内存占用太多,cleanExpiredEntry的时候gc卡了?

我们也跟踪了这个读写锁,都是try finally的,都会被释放,所以跟踪代码完全看不到问题,而实际上却发现运行这个cleanExpiredEntry的线程处于就绪运行状态,其他获取数据的读锁都在等待写锁.

测试环境上面是不能进行本地远程调试的,没有打断点的可能性.至于说gc卡了,这个就不好说,忘记截图了,当时通过prometheus监控服务器的状态未发现问题,gc也很正常.

大佬,您觉得还有什么其他可能呢?

areyouok commented 3 months ago

我看不出有什么问题,你需要排除一下其它原因。当然用caffeine也是很好的,性能肯定比我这个手撸的简单货强。

LinkedHashMapCache的意义在于,你不想引入额外依赖,大部分时候LinkedHashMapCache足够了。

Charles5563 commented 3 months ago

我看不出有什么问题,你需要排除一下其它原因。当然用caffeine也是很好的,性能肯定比我这个手撸的简单货强。

LinkedHashMapCache的意义在于,你不想引入额外依赖,大部分时候LinkedHashMapCache足够了。

感谢大佬百忙中抽空看我提的这个,那我们就先用caffeine,谢谢