异常场景
springWeb应用一直运行正常,同事最近反应,每次版本更新完毕,刷新缓存,就会导致应用挂死。只有重启redis应用才恢复正常。
项目概况
springWeb项目,常用配置表做了redis缓存,配置表中只有少量数据,多也就是1~2K条记录。用redission做分布式锁,与缓存用的同一个reids服务。
分析过程
由于系统之前一直稳定运行,只是最近才有问题,先从异常日志查起吧。通过查看tomcat日志,发现大量redis time out异常。起初怀疑是不是网络原因?但是通过几次更新,有下午,有晚上,而且如果有网络问题,其他应用也会有问题。所以排除了网络原因。
怀疑redis连接池配置是否有问题,通过查看redission配置,dnsMonitoring参数在低版本的redission中可能导致超时异常。但是通过设置该参数,并未解决问题。
镜头再回到刚开始,询问同事,每次都是一操作web端的缓存管理-->清除缓存,系统就挂死。从web端重新开始分析,但是貌似也看不出什么问题。
ICacheManager cacheManager = getCacheManager();ICache cache = cacheManager.getICache(cacheName);cache.clear();
再统计一下redis中存放的key确实不少,分类统计了下,发现缓存数量与实际表中的配置数量差异比较大。
于是有这样的假设:清空全部缓存,会先读出缓存列表,逐个清除,如果缓存比较大就有可能把系统搞死。然后找证据。
通过redisDesktopManager,查看数量最多的key和value,发现缓存的key怎么是对象?value为空的list?
xACxEDx00x05tx00x7FCustomSimpleKey_getSecondTypesCache(class xxx.Param)[{[object Object]=null, timestamp=1589501938332}]
再查看缓存接口,发现缓存失效策略为永久生效。
原来spring缓存接口自动会把接口方法名+参数当作key,db查询结果当作value缓存。
@ICacheable(cacheSeconds = Constant.FOREVER)public List> getxxxById(String id){return dao.select("conf1", id);}
至此,就可以解释了。缓存的key随着入参的不同,会越来越多,而且不会失效。前台操作清除缓存时,缓存管理器会把所有key读入内存,逐个清除。由于redis是单线程运行的,在清除缓存的过程中,redis不能对外提供服务,所有用到缓存的地方,请求不到redis连接,就报timeout异常了。
其实这和web端列表展示要分页是一个道理,如果不分页,直接从db读取一个大list,本身就是欠考虑的操作。
解决方案
- redis缓存一定要设置失效时间,不要设置成永久生效;
- web端清除缓存操作优化
- reids监控还是得加上,尤其是这种数量比较大的key,应该增加告警机制
此处可以使用ELKStack中的metricbeat之redis模块,配合kibana仪表盘展示,一目了然,还可以告警,妥妥的。