一、背景
最近一段时间公司有个服务频繁出现"com.mongodb.MongoWaitQueueFullException:Too many threads are already waiting for a connection. Max number of threads (maxWaitQueueSize) of 100 has been exceeded",程序在获取连接的排队队列设置的100,连接使用时间过长导致获取连接溢出,
经DBA分析排除mongo 慢查询原因,对比异常出现时间跟redis监控cpu过高想吻合,怀疑redis rdb持久化导致,导出rdb,通过rdbtools分析工具做内存分析,查出内存占用最高前10个key,发现排第一的key 占用3G内存并且无过期时间hash结构key-value对有65W数据,经沟通此key数据可清理,清理内存,持久1天观察日志,问题解决.
二、排查过程
1.让dba导出rdb,本次导出文件名8140.rdb
2.安装redis内存分析工具rdbTools,此工具python所写,需要有python环境,安装完毕,通过命令“rdb --help”校验是否安装成功
3.开发库新建表,已创建,用于分析redis内存使用情况
|
4.将rdb上传机器,通过rdbtools生成内存统计数据报告,执行"rdb -c memory /data/pub/ytl/rdb/8140.rdb > memory.csv"
5.将生成好的memory.csv导入开发库表memory
6.通过以下sql分析各种数据
1、查询key的个数
select count(*) from memory;
2、查询总的内存占用
select sum(size_in_bytes) from memory;
3、查询内存占用最高的3个key
select * from memory order by size_in_bytes desc limit 3;
4、查询value个数1000个以上的hash
select * from memory where type='hash' and num_elements > 1000;
通过查询内存占用最高的key发现已占用3G内存,65W的k-v对并且没有过期时间,通过业务排查可以清除,处理后cpu就降下来了
三、hash处理过期时间失效问题
导致hash主key失效的几种可能,本次出现的过期失效属于第一种情况
1.经检查代码在spring容器初始化对象时直接expire给key设置了过期时间,此处逻辑有问题,第一没有判断redis是否存在此key,没有key时就设置操作无效,第二当redis的put操作不会刷新key的过期时间,当key过期后的put就会导致key长期有效
代码验证
1.1先创建个hash设置过期时间
1.2在put查看过期时间是否被刷新
控制台打印
可以得出结论,put时不会刷新hash的过期时间,这样可以推理出当程序运行到过期时间到了后,key被删除后后续的所有put的数据都是长期有效的,这样导致这个hashkey会越来越大,知道服务重启重新设置key的过期
2.hash的subkey被清空时会触发主key的删除
代码验证
2.1 先创建一个hash结构数据并设置过期时间
查询redis看下效果
2.2 在清除hash里唯一的一条subkey,在查看redis
查询redis
我们发现在hash的subkey被清理光时,主key也会被清理掉
2.3 我们在put一条数据,完整的模拟业务代码逻辑就是先创建hash的主key,在设置过期时间,在put一条数据
key被长期有效
所以在put数据的时候需要判断当前hashkey是否存在,不存在就要加过期时间,防止生成ttl:-1的key