缓存雪崩防治需三重策略:一设随机过期时间防集体失效;二用带过期时间的互斥锁控重建节奏;三建双层缓存+预热监控兜底降级。

设置随机过期时间:别让所有 key 在同一秒“集体辞职”
缓存雪崩最常见诱因,就是批量写入的 key 共享同一个 TTL,比如凌晨 2:00 整齐划一地过期。数据库瞬间收到上万请求,CPU 直接拉满——这不是压力测试,是误伤。
解决方法很简单:给每个 key 的过期时间加点“扰动”。不是固定 3600 秒,而是 3600 + random.randint(0, 600)(即 1 小时 ± 10 分钟)。
- 基础 TTL 越长,随机偏移量建议按比例放大(例如 24 小时 TTL 可配 ±30 分钟,而非 ±10 秒)
- 避免使用
time.time() // 3600这类取整逻辑生成 key,否则仍会聚簇失效 - Java 用户注意:
Random.nextInt(n)返回的是[0, n),别写成nextInt(600) + 1导致下限偏移 - Python 示例中若用
random.randint(a, b),要确认a ≤ b,否则抛ValueError
用互斥锁控制重建节奏:只放一个人进数据库“厨房”
缓存失效后,如果 100 个并发请求都去查数据库,再各自写回 Redis,既浪费资源,又可能因写入顺序混乱导致数据不一致。真正需要的,是让第一个请求去加载,其余等待结果。
关键不是“有没有锁”,而是“锁怎么设才不翻车”:
- 必须设置锁的过期时间(
ex参数),否则服务卡死、进程崩溃时锁无法释放,后续所有请求永久阻塞 - 推荐锁过期时间略大于预期 DB 查询耗时(如 DB 平均 800ms,设
ex=5秒足够,别设 30 秒) - 不要用
sleep(0.1); retry无限递归,容易堆栈溢出;改用有限重试(如最多 3 次)+ fallback 值 - Redis 的
setnx已被标记为 legacy,生产环境优先用SET key value EX seconds NX命令
双层缓存兜底:主缓存挂了,备份还能撑一会儿
当 Redis 整体不可用(网络分区、OOM kill、配置错误导致全量 flush),单靠随机 TTL 和互斥锁已无济于事。此时需要“降级通道”——让请求不至于全部砸向数据库。
典型做法是分两级缓存:
- 主缓存(
main:{key}):TTL 短(如 30 分钟),走 Redis,负责高性能读取 - 备份缓存(
backup:{key}):TTL 长(如 24 小时),也走 Redis,但仅在主缓存 miss 且 DB 查询成功后异步更新 - 读逻辑:先查
main→ 缺失则查backup→ 若backup也缺,才查 DB 并同步写入两级缓存 - 注意:备份缓存不能“永久不过期”,否则脏数据永远滞留;它只是比主缓存多扛一阵子
预热 + 监控:别等大促开始才想起缓存还空着
随机 TTL 和互斥锁防的是“失效后乱”,但更优解是“别让它失效”——尤其对确定的热点数据(首页 Banner、秒杀商品、配置中心)。这些不该依赖用户请求来触发加载。
实操要点很实在:
- 预热任务应避开业务高峰,比如选在凌晨 1:00–3:00 执行;脚本需带失败重试和告警(如某 key 预热失败超 5 次发钉钉)
- 预热用的 TTL 也要加随机值,否则预热脚本自己就成了雪崩源头
- 必须监控缓存命中率突降、DB QPS 异常飙升、Redis
evicted_keys指标激增——这些才是雪崩前的真实信号,不是等报警电话打进来才反应 - 本地缓存(如 Caffeine)可作为第三道防线,但它无法解决跨实例一致性问题,仅适合读多写少、容忍短暂不一致的场景
真正难的不是写对那几行 SET 或 SETNX,而是把随机值算得合理、把锁超时设得刚好、把预热时机卡得准——这些细节没有银弹,全靠线上反馈反复调校。








