spring缓存@Cacheable死锁问题

场景:生产的某个系统突然出现feign超时异常,粗略查看日志得知是A服务调用B服务出现超时异常,将feign超时设置为一分钟,关闭熔断器异常仍出现超时情况,确认是服务内部异常。继续排查发现A服务发起的请求确实是进入到B服务了,但是进入到B服务之后只执行了一小段代码就没日志信息,也无报错信息,一分钟后A服务连接超时,令人匪夷所思。

排查过程
1、通过top命令查看系统资源占用情况,发现某个java进程占用资源较大,CPU使用率及内存使用较高,怀疑是系统资源紧张导致服务异常情况,打开异常服务日志搜索关键字,查看是否存在内存溢出情况。
在这里插入图片描述
2、此时怀疑是某个模块代码问题导致内存溢出,遂重启服务器,期望能解决问题。服务重启之后top、free指令查看服务器资源,此时服务器资源回归正常。重新调用接口发现超时现象未解决,开始怀疑B服务执行方法到某段代码线程堵塞了。
3、重新发起测试,top指令查看占用CPU和内存最高的java进程的pid,通过jstack命令输出线程执行情况。

jstack -pid > error.log

在这里插入图片描述
4、查看输出文件内容,发现存在大量的等待线程,图片高亮部分阔以分析得出程序执行到CacheOperator这个类的第160行代码的时候,线程开始进入等待,RedisCache执行了waitForLock。现在已经比较明了定位到程序问题所在了,查看160行代码分析异常。
在这里插入图片描述
5、第160行代码是使用到了spring的cache缓存,使用了@Cacheable注解,查看具体的代码
在这里插入图片描述
6、该注解使用到了sync = true属性,sync默认为false,当为true的时候程序执行到该方法会先查看redis中是否有缓存,若无缓存则只允许一个线程进入数据库查询(这时候会给redis上锁,防止其他请求进来查询数据库),本身这个功能没啥问题,能减少数据库压力。怀疑是这里导致线程堵塞,直接复制cashNames到redis数据库中查询是否有锁,spring Cache的锁默认是 cacheNames~lock 形式,如:
在这里插入图片描述
7、将redis中的锁删除,重新测试程序恢复正常。

出现原因
缓存注解中设置 sync=true,默认只有一个线程进去查询,若该线程查询数据库由于网络或者环境因素导致线程中断而没有释放锁,那么后续进来的所以请求都会被拦截。这方面我觉得spring的缓存机制阔以优化下,来个超时策略。因为所有的请求到了这里都获取不到锁进入等待阶段,请求堆积了导致服务器资源爆满,开始出现内存溢出。主要难排查还是在于spring缓存也没个提醒,不知道一直锁着,一直不管的话会导致整个服务器资源爆满进而影响到其他服务。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐