南京网站建设网站制作 雷仁网络,什么软件做网站做好,政务服务网站建设情况汇报,如何做线上销售线上Redis响应时间从平均1ms飙到了50ms#xff0c;业务接口全都变慢了。
查了半天#xff0c;最后发现是一个BigKey导致的。记录一下排查过程。问题现象
监控数据#xff1a;
Redis平均响应时间#xff1a;1ms → 50ms业务接口P99延迟#xff1a;50ms → 500msRedis CPU业务接口全都变慢了。查了半天最后发现是一个BigKey导致的。记录一下排查过程。问题现象监控数据Redis平均响应时间1ms → 50ms业务接口P99延迟50ms → 500msRedis CPU20% → 80%内存使用正常特点突然变慢不是逐渐变慢所有命令都变慢不只是特定命令重启后好一段时间然后又变慢排查过程Step 1查看慢查询日志redis-cli# 查看慢查询日志SLOWLOG GET20输出1) 1) (integer) 1001 2) (integer) 1702345678 3) (integer) 45123 # 微秒约45ms 4) 1) HGETALL 2) user:session:12345发现大量HGETALL命令耗时几十毫秒正常应该是亚毫秒级。Step 2查看这个Key的信息# 查看Key类型TYPE user:session:12345# hash# 查看Hash的字段数量HLEN user:session:12345# 182356# 查看Key占用内存DEBUG OBJECT user:session:12345# serializedlength:15728640 约15MB问题找到了这个Hash有18万个字段占用15MB内存。这就是BigKey对它执行HGETALL要把18万个字段全部遍历当然慢。Step 3查找其他BigKey# Redis 4.0 可以用 --bigkeys 扫描redis-cli --bigkeys# 或者用 SCAN 配合 DEBUG OBJECTredis-cli --scan --pattern*|whilereadkey;dosize$(redis-cli DEBUG OBJECT$key2/dev/null|grep-oPserializedlength:\K\d)if[$size-gt1048576];then# 大于1MBecho$key:$sizebytesfidone扫描结果发现了多个BigKeyuser:session:12345: 15728640 bytes (15MB) cache:product:list: 8388608 bytes (8MB) temp:import:batch: 5242880 bytes (5MB)Step 4分析业务逻辑查代码发现问题// 问题代码把整个session存成一个大HashOverridepublicvoidsaveSession(StringsessionId,MapString,Objectdata){Stringkeyuser:session:sessionId;// 每次访问都往里加数据从来不清理redisTemplate.opsForHash().putAll(key,data);}// 获取时用HGETALLpublicMapString,ObjectgetSession(StringsessionId){Stringkeyuser:session:sessionId;returnredisTemplate.opsForHash().entries(key);// HGETALL}问题Session数据一直往Hash里加不删除时间一长Hash就变成了BigKey每次获取Session都用HGETALL遍历整个HashBigKey的危害1. 阻塞单线程Redis是单线程的操作BigKey时会阻塞其他命令正常Key1KB 1ms完成 BigKey10MB 50ms完成 这50ms内其他所有命令都在排队等待2. 网络带宽压力每次HGETALL返回15MB数据 1秒请求10次 150MB/s 网络可能成为瓶颈3. 内存不均衡如果是Redis集群BigKey会导致某个节点内存远大于其他节点。4. 删除时阻塞DEL user:session:12345# 删除15MB的Key可能阻塞好几秒解决方案方案一拆分BigKey把大Hash拆成多个小Hash// 优化前一个大Hashuser:session:12345→{field1:v1,field2:v2,...field180000:v180000}// 优化后按照某种规则拆分user:session:12345:0→{field1:v1,...field1000:v1000}user:session:12345:1→{field1001:v1001,...field2000:v2000}...方案二改用合适的数据结构Session数据不需要存18万个字段只需要保留最近访问的数据// 使用String存储序列化后的数据设置过期时间publicvoidsaveSession(StringsessionId,SessionDatadata){Stringkeyuser:session:sessionId;StringjsonJSON.toJSONString(data);redisTemplate.opsForValue().set(key,json,30,TimeUnit.MINUTES);}方案三避免HGETALL// 优化前获取整个HashMapString,ObjectallredisTemplate.opsForHash().entries(key);// 优化后只获取需要的字段ObjectvalueredisTemplate.opsForHash().get(key,targetField);// 或者批量获取部分字段ListObjectvaluesredisTemplate.opsForHash().multiGet(key,Arrays.asList(f1,f2));方案四异步删除BigKey# Redis 4.0 支持异步删除UNLINK user:session:12345# 异步删除不阻塞# 或者渐进式删除Hash# 每次删1000个字段HSCAN user:session:123450COUNT1000HDEL user:session:12345 field1 field2... field1000最终解决临时处理用UNLINK异步删除那几个BigKey代码修复Session改用String存储设置30分钟过期添加监控定期扫描BigKey超过1MB告警BigKey标准数据类型BigKey阈值说明String 10KB单个值太大Hash 5000字段 或 10MB字段太多或总大小太大List 5000元素元素太多Set 5000成员成员太多ZSet 5000成员成员太多排查命令汇总# 查看慢查询SLOWLOG GET20# 扫描BigKeyredis-cli --bigkeys# 查看Key类型TYPEkey# 查看Hash字段数HLENkey# 查看List长度LLENkey# 查看Set成员数SCARDkey# 查看内存占用需要开启MEMORY USAGEkey# 查看Key详情DEBUG OBJECTkey# 渐进式扫描HSCANkey0COUNT100# 异步删除UNLINKkey预防措施1. 设计阶段✅ 预估数据量避免无限增长 ✅ 设置合理的过期时间 ✅ 考虑数据拆分策略2. 开发阶段✅ 避免使用HGETALL、SMEMBERS等全量命令 ✅ 大数据量使用SCAN系列命令 ✅ 删除大Key使用UNLINK3. 运维阶段✅ 定期扫描BigKey ✅ 监控慢查询 ✅ 设置maxmemory-policy经验总结现象可能原因所有命令都变慢BigKey阻塞特定命令变慢该命令操作了BigKey内存突然增长写入了BigKey主从同步延迟BigKey传输这次的坑Session数据只写不删时间一长变成了18万字段的BigKey。教训Redis的Key一定要设置过期时间避免使用HGETALL等全量命令定期扫描BigKey加入监控有问题评论区交流~