实战-用户中心-l2cache-Caffeine的OOM异常分析

l2cache 源码地址

一、概要

1、电商平台,有几千万的用户量。

2、下单时,会调用用户服务获取用户信息。

3、用户服务通过 l2cache 二级缓存框架 来缓存用户信息。

注:l2cache 为自研二级缓存框架
一级缓存(本地缓存): caffeine
二级缓存(分布式缓存):redis

4、本地缓存caffeine的最大元素大小设置为5000,过期时间为30分钟。

二、问题

通过阿里云ARMS监控发现,用户服务存在频繁FullGC和YoungGC的情况,最终导致OOM。

三、分析

结合业务场景进行分析,发现用户维度的本地缓存的命中率非常低,相当于是每个用户请求都会打到redis或db上。
并且当本地缓存达到5000个元素的限制,这时若有大量不同用户请求,会触发caffeine的最大元素淘汰机制(异步),若未及时淘汰,经历过几次YoungGC后,缓存对象从Young区转移到Old区,导致Old区飙升,出现频繁的FullGC和YoungGC,最终导致OOM。

四、方案

1、直接使用Redis作为缓存即可,无需本地缓存。

l2cache:
  config:
    cacheType: redis

2、本地缓存不适用于数据量大且命中率极低的缓存场景,如用户维度缓存。

五、总结

1、l2cache 二级缓存并不是一个万能药

  • 适用场景:缓存项命中率较高的业务场景

举例:如商品维度缓存,因为不同用户看到的是同一个商品,所以命中率高。

  • 不适用场景:缓存项命中率低的业务场景

举例:如用户维度缓存,因为不同用户只能获取自己的用户信息,所以命中率低。

2、一定要结合业务场景来分析是否需要用到二级缓存

六、思考:

为什么caffeine超过最大元素限制后,缓存项没有被及时淘汰掉,而是进入到了Old区,最终导致频繁的GC和OOM出现呢?

  • 猜想:

1、需要被淘汰的缓存项是通过异步的方式去清理的

2、为了提高资源利用率,通过线程池来进行异步清理

3、缓存达到最大元素限制后,若还有大量不能命中缓存的请求,则会加载数据并put到缓存后,会产生大量缓存项淘汰任务(基于大小的过期)

4、在线程池处理不过来淘汰任务的情况,会出现缓存项堆积,最终导致出现频繁的GC,甚至OOM

  • 分析

这种问题一般很难百度到,所以结合百度和caffeine源码来进行分析。

com.github.benmanes.caffeine.cache.LocalAsyncLoadingCache.get(key)

以 LoadingCache.get(key) 为入口进行分析

com.github.benmanes.caffeine.cache.BoundedLocalCache.replace(key, value)

替换缓存值

com.github.benmanes.caffeine.cache.BoundedLocalCache.afterWrite(Runnable task)

缓存值写入后的处理

com.github.benmanes.caffeine.cache.BoundedLocalCache.scheduleDrainBuffers()

尝试执行一个异步任务PerformCleanupTask,以将挂起的操作采用替换策略。

executor.execute(drainBuffersTask);
// 此处可证明通过线程池的方式来进行任务处理。

如果执行器拒绝任务,则直接运行 maintenance(Runnable)。
// 此处可证明超过任务等待队列大小后,会进行补偿处理,同时可证明堆积了大量清理任务,也证明了超过缓存大小限制后有大量缓存没有被及时清理掉。

com.github.benmanes.caffeine.cache.BoundedLocalCache.PerformCleanupTask

执行清理任务。此类可证明通过异步任务的形式来处理。

com.github.benmanes.caffeine.cache.BoundedLocalCache.performCleanUp(Runnable task)

由于线程池的所有线程都很忙,清理任务可能已被调度但未运行。如果所有线程都在写入缓存,那么没有帮助就无法取得任何进展。所以该方法 实际调用 maintenance(Runnable) 来进行清理。

com.github.benmanes.caffeine.cache.BoundedLocalCache.maintenance(Runnable task)

具体的清理任务。清空读取缓冲区、写入缓冲区和引用队列,然后是过期和基于大小的逐出。

com.github.benmanes.caffeine.cache.BoundedLocalCache.evictEntries()

如果缓存超过最大值,则逐出条目。

com.github.benmanes.caffeine.cache.BoundedLocalCache.PerformCleanupTask

执行清理任务

  • 结论

通过上面的分析,可以发现我的猜想全部得到证实。由此我们可以得出如下结论:

使用 caffeine 缓存命中率低的缓存项时,在超过缓存大小限制后,若还有大量请求通过 caffeine 获取值,那么可能会出现缓存项没有被及时清理掉的情况,最终导致频繁的GC,甚至OOM。

所以,caffeine 不适用于数据量大,并且缓存命中率极低的业务场景,如用户维度的缓存。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白云coy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值