Redis大key删除引发的服务雪崩,这次我真记住了

55 阅读1分钟

凌晨3点,报警短信炸了——线上核心服务响应时间从200ms飙升至20秒,下游服务大面积超时。登录机器一看,Redis内存使用率从70%骤降到30%,而CPU却冲到100%。谁能想到,这一切仅仅是因为我们删了一个300MB的Hash key?

一、灾难现场还原

某次大促前,我们决定清理一批历史缓存。通过扫描工具发现一个存储用户行为数据的Hash key(user_actions:2023),内含2000万字段,体积达300MB。团队中的小张顺手写了段Python脚本执行删除:

# 灾难代码:直接DEL大Key
redis_client.delete("user_actions:2023")

10秒后,监控大屏全盘飘红。

二、为什么删除Key会引发雪崩?

  • 根因在于Redis单线程模型与阻塞式内存回收机制*:
  1. 同步删除的代价
    • 执行DEL时,Redis主线程需要同步释放该key对应的所有内存,300MB的内存释放会让主线程卡住约1.8秒(实测Redis 6.0,16核机器)
    • 所有客户端请求在这期间排队,超时连锁反应开始传导
  2. 更致命的OOM连锁反应
    • 大key删除后内存骤降,但此时如果有大量写请求涌入,Redis会:
      • 优先用jemalloc分配此前释放的内存块(可能触发内存整理)
      • 突然的内存压力可能引发子进程RDB/AOF重写失败
  3. 网络带宽冲击(附加伤害)
    • 如果这个key在从库也存在,主库删除后会同步一条DEL命令到从库
    • 某些Redis版本从库处理大key删除时,会导致主从复制缓冲区堆积
# 用以下命令可以复现卡顿(慎用生产环境)
redis-cli --bigkeys | grep -i hash  # 先找大key
time redis-cli del your_large_key   # 观察耗时

三、高手解法:渐进式删除方案

正确姿势是分段扫描+异步删除,核心思路是避开主线程阻塞。以下是经过实战验证的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)主线程阻塞峰值内存抖动
直接DEL1.8秒100%300MB↓
分批HDEL(1000/批)32秒<5%平稳释放

(测试环境:Redis 6.2,阿里云8C16G实例)

五、避坑指南:大key删除的5个要点

  1. 永远不要在线流量高峰操作:即便用渐进式删除,也会有额外CPU开销
  2. 优先用UNLINK代替DEL:Redis 4.0+的UNLINK是后台异步删除(但要注意版本兼容性)
  3. 警惕集群模式下的跨节点key:如果大key是分片存储,需要遍历所有相关节点
  4. 删除前先摘除流量:通过CLIENT PAUSE暂停客户端连接(Redis 3.2+支持)
  5. 监控内存碎片率:频繁删除大key可能导致mem_fragmentation_ratio飙升,必要时重启

六、复盘时刻

这次事故让我彻底明白:在Redis的世界里,删除比写入更危险。现在团队内强制要求:所有超过1MB的key操作必须走审批流程,并在临时从库上验证影响。

你在项目里遇到过大key的坑吗?欢迎分享你的血泪史——毕竟,这才是工程师的真正成长方式。