凌晨3点,报警短信炸了------线上核心服务响应时间从200ms飙升至20秒,下游服务大面积超时。登录机器一看,Redis内存使用率从70%骤降到30%,而CPU却冲到100%。谁能想到,这一切仅仅是因为我们删了一个300MB的Hash key?
一、灾难现场还原
某次大促前,我们决定清理一批历史缓存。通过扫描工具发现一个存储用户行为数据的Hash key(user_actions:2023),内含2000万字段,体积达300MB。团队中的小张顺手写了段Python脚本执行删除:
python
# 灾难代码:直接DEL大Key
redis_client.delete("user_actions:2023")
10秒后,监控大屏全盘飘红。
二、为什么删除Key会引发雪崩?
- 根因在于Redis单线程模型与阻塞式内存回收机制*:
- 同步删除的代价
- 执行
DEL时,Redis主线程需要同步释放该key对应的所有内存,300MB的内存释放会让主线程卡住约1.8秒(实测Redis 6.0,16核机器) - 所有客户端请求在这期间排队,超时连锁反应开始传导
- 执行
- 更致命的OOM连锁反应
- 大key删除后内存骤降,但此时如果有大量写请求涌入,Redis会:
- 优先用
jemalloc分配此前释放的内存块(可能触发内存整理) - 突然的内存压力可能引发子进程RDB/AOF重写失败
- 优先用
- 大key删除后内存骤降,但此时如果有大量写请求涌入,Redis会:
- 网络带宽冲击(附加伤害)
- 如果这个key在从库也存在,主库删除后会同步一条
DEL命令到从库 - 某些Redis版本从库处理大key删除时,会导致主从复制缓冲区堆积
- 如果这个key在从库也存在,主库删除后会同步一条
bash
# 用以下命令可以复现卡顿(慎用生产环境)
redis-cli --bigkeys | grep -i hash # 先找大key
time redis-cli del your_large_key # 观察耗时
三、高手解法:渐进式删除方案
正确姿势是分段扫描+异步删除,核心思路是避开主线程阻塞。以下是经过实战验证的Java实现:
java
public void safeDeleteLargeHash(String key, int batchSize) {
try (Jedis jedis = jedisPool.getResource()) {
// 游标式分批删除
String cursor = "0";
do {
ScanResult<Map.Entry<String, String>> scanResult =
jedis.hscan(key, cursor, new ScanParams().count(batchSize));
// 异步删除字段(用pipeline减少网络往返)
Pipeline pipeline = jedis.pipelined();
scanResult.getResult().forEach(entry -> pipeline.hdel(key, entry.getKey()));
pipeline.sync();
cursor = scanResult.getCursor();
} while (!"0".equals(cursor));
// 最后删除空key
jedis.del(key);
}
}
- 关键细节*:
SCAN命令的count参数只是建议值,实际返回数量可能浮动- 每批删除后建议加
Thread.sleep(100),避免CPU毛刺 - Pipeline能压缩网络包,但总吞吐量仍受Redis单线程限制
四、性能对比数据
| 方案 | 耗时(300MB Hash) | 主线程阻塞峰值 | 内存抖动 |
|---|---|---|---|
| 直接DEL | 1.8秒 | 100% | 300MB↓ |
| 分批HDEL(1000/批) | 32秒 | <5% | 平稳释放 |
(测试环境:Redis 6.2,阿里云8C16G实例)
五、避坑指南:大key删除的5个要点
- 永远不要在线流量高峰操作:即便用渐进式删除,也会有额外CPU开销
- 优先用UNLINK代替DEL :Redis 4.0+的
UNLINK是后台异步删除(但要注意版本兼容性) - 警惕集群模式下的跨节点key:如果大key是分片存储,需要遍历所有相关节点
- 删除前先摘除流量 :通过
CLIENT PAUSE暂停客户端连接(Redis 3.2+支持) - 监控内存碎片率 :频繁删除大key可能导致
mem_fragmentation_ratio飙升,必要时重启
六、复盘时刻
这次事故让我彻底明白:在Redis的世界里,删除比写入更危险。现在团队内强制要求:所有超过1MB的key操作必须走审批流程,并在临时从库上验证影响。
你在项目里遇到过大key的坑吗?欢迎分享你的血泪史------毕竟,这才是工程师的真正成长方式。