Redis 缓存雪崩、击穿、穿透:三大故障排查与防护

作为高并发架构的标配,Redis 一旦遭遇缓存雪崩、缓存击穿、缓存穿透这三类典型故障,后端数据库会瞬间被打垮,引发全站雪崩。本文结合一线排障经验,逐一拆解三类故障的成因、定位手段与防护方案,并给出可直接落地的代码与检查清单。

一、缓存雪崩:大量 key 在同一时刻集体失效

1.1 故障成因

缓存雪崩指的是在某一时刻,大量缓存 key 同时过期,或者 Redis 实例整体宕机,导致所有请求直接打到数据库。数据库瞬时 QPS 暴涨数倍甚至数十倍,往往扛不住直接被打死,进而引发连锁反应,整个服务雪崩。最常见的诱因是「批量数据初始化时设置了相同的 TTL」,或缓存服务重启后未预热。

1.2 如何快速定位

  • 监控 Redis 命中率(hit rate)在故障时间点骤降;
  • 数据库 QPS、CPU 在同时段突增,出现慢查询告警;
  • 应用日志大量出现 key not found,随后 DB 连接池被打满。

配合 Redis 缓存设计规范 中的监控埋点,可以更快区分「集体失效」与「服务宕机」。当穿透把压力传导到数据库侧时,也可参考 PostgreSQL 慢查询优化 的定位思路反向排查。

1.3 防护:错峰过期 + 多级兜底

核心思路是让 key 的过期时间错开,并对底层加一层保护。给基础 TTL 叠加随机抖动,避免集体失效:

import random

def set_cache_with_jitter(r, key, value, base_ttl=3600, jitter=300):
    # base_ttl 基础上叠加 ±jitter 的随机过期,避免集体失效
    ttl = base_ttl + random.randint(-jitter, jitter)
    r.setex(key, ttl, value)

# 基础 1 小时 + 最多 ±5 分钟抖动,批量写入时过期时间被打散
set_cache_with_jitter(redis_client, "product:1001", data)

此外,接入本地内存(如 Caffeine)作为 L1、Redis 作为 L2 的多级缓存,可在 Redis 抖动时兜底;并配合熔断(如 Sentinel / Resilience4j)在数据库压力过大时快速失败,保护 DB 不被拖垮。

二、缓存击穿:单个热点 key 失效的瞬间

2.1 与雪崩的本质区别

缓存击穿不是「大量 key」,而是某一个被高并发访问的热点 key 在过期瞬间,成百上千的请求同时发现缓存没了,一起穿透到数据库去重建缓存。单 key 的瞬时冲击足以拖垮 DB。典型场景:爆款商品详情、首页热点配置、秒杀库存。

2.2 方案 A:互斥锁重建(SingleFlight 思路)

只允许一个线程去查库并重建缓存,其余请求等待或返回旧值。基于 Redis SET key value NX 实现分布式锁:

# 尝试获取重建锁,10 秒自动释放,避免死锁
SET cache:lock:product:1001 1 NX EX 10
# 拿到锁的线程查库并写回缓存,随后释放锁
SET product:1001 "{...}" EX 3600
DEL cache:lock:product:1001

Java 中更推荐用 Redisson 的 RLock 或 Guava Striped + synchronized,避免自己处理锁续期与误删。Go 可直接用 singleflight 包,天然解决并发重建。

2.3 方案 B:逻辑过期(永不过期 + 异步刷新)

不给 key 设物理 TTL,而是在 value 中存入逻辑过期时间;发现逻辑过期时提交异步线程重建,当前请求直接返回旧值。优势是无锁、响应快,代价是可能短暂返回旧数据,适合容忍弱一致性的场景。

三、缓存穿透:查的是根本不存在的数据

3.1 成因与危害

缓存穿透指查询一个数据库中也不存在的 key(如恶意刷 id=-1、或越权遍历)。因为查不到,缓存永远不会被写入,每次请求都穿透到 DB。若遭遇脚本批量扫库,DB 会被活活拖死,且常规限流往往拦不住这种「合法但无效」的查询。

3.2 防护一:缓存空值

对查不到的结果也写入一个短期空值(如 NULL 或特殊标记),并设置较短 TTL,挡住重复查询:

public String getWithNilGuard(String key) {
    String val = redis.get(key);
    if (val != null) {
        return "NIL".equals(val) ? null : val;   // 命中空值标记,直接拦截
    }
    val = db.query(key);                          // 查库
    if (val == null) {
        redis.setex(key, 60, "NIL");              // 缓存空值,60s 防穿透
    } else {
        redis.setex(key, 3600, val);
    }
    return val;
}

3.3 防护二:布隆过滤器

在缓存之前加一层布隆过滤器,非法 id 直接拦截,连 Redis 都不用访问。适合 id 空间有限、可预热的场景:

// 写入时同步放入布隆过滤器
bloomFilter.put(productId);
// 查询前先判断,一定不存在的直接拦截
if (!bloomFilter.mightContain(productId)) {
    return null;  // 100% 不存在,杜绝穿透
}
String val = redis.get("product:" + productId);

四、三类故障对比速查

维度缓存雪崩缓存击穿缓存穿透
触发条件大量 key 同时失效 / Redis 宕机单热点 key 失效瞬间查询不存在的数据
影响范围全局、最严重单热点、局部单类非法请求
典型现象命中率骤降 + DB QPS 暴涨单 key 查库 QPS 尖刺DB 持续被空查
核心防护错峰过期 + 多级缓存 + 熔断互斥锁 / 逻辑过期空值缓存 + 布隆过滤器

五、生产落地检查清单

检查项建议做法
过期策略基础 TTL + 随机抖动,禁止批量相同 TTL
热点保护热点 key 永不过期 + 逻辑过期异步刷新
空值处理查空也写短期空值,封掉重复穿透
非法拦截接入布隆过滤器,前置拦截
降级熔断DB 压力大时熔断,返回兜底数据
监控告警命中率 + DB QPS + 慢查询联动告警

将缓存压测纳入 GitHub Actions CI,在每次发版前用模拟流量验证击穿与雪崩防护是否生效;部署侧则可结合 Docker 容器化 与 Nginx 反向代理 做统一的限流与超时控制。

六、真实排障案例:一次大促前的缓存演练

某次大促前的全链路压测中,监控发现 Redis 命中率从平时的 99% 骤降到 60%,数据库 CPU 随之冲高。排查定位为「批量商品预热任务」给上万 key 写入了完全相同的 2 小时 TTL,导致整点集体失效、瞬间雪崩。修复方案是在预热写入时引入随机抖动(见 1.3 节),并把头部热点商品改为逻辑过期 + 异步刷新。改完后再次压测,命中率稳定在 98% 以上,数据库峰值 QPS 下降约 70%。这个案例说明:缓存故障往往不是「没做防护」,而是「防护在边界条件下失效」,必须在发版前用真实流量模型反复验证,而不是上线后等问题暴露。

七、总结

缓存雪崩、击穿、穿透是 Redis 高并发场景下最高频的三类故障,但成因与解法各不相同:雪崩重「错峰与兜底」,击穿重「单点互斥」,穿透重「空值与过滤」。把它们纳入上线前的缓存设计评审,配合 Redis 缓存设计规范 的标准化实践,才能在流量洪峰来临时稳如磐石。

上一篇 Cilium 网络策略实战:用 eBPF 打造 Kubernetes Zero-Trust 安全
下一篇 Spring Security 实战:JWT 认证与权限控制
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]