2. 缓存一致性基础:缓存穿透、缓存击穿、缓存雪崩的定义与典型场景

在分布式系统中,缓存是提升读取性能、保护后端数据库的关键手段。然而,缓存与数据库之间的数据不一致、缓存失效的并发冲击,会引发三类经典故障模式:缓存穿透、缓存击穿和缓存雪崩。理解这三者的定义、成因与典型场景,是设计缓存一致性治理与错误处理链路的前提。

2.1 三类故障的定义与核心差异

故障类型 定义 核心成因 影响范围
缓存穿透 请求查询一个缓存和数据库中都不存在的数据,导致每次请求都穿透缓存直达数据库。 恶意攻击或业务漏洞,查询不存在的主键/索引。 数据库压力陡增,可能拖垮 DB。
缓存击穿 一个热点 key 在缓存失效的瞬间,大量并发请求同时回源数据库,导致数据库负载瞬间飙升。 热点 key 过期 + 高并发访问。 单点数据库压力激增,可能引发局部故障。
缓存雪崩 大量缓存 key 在同一时间或短时间内集中失效,导致海量请求直接打到数据库,引发系统级崩溃。 缓存服务宕机、统一过期时间设置不当。 全系统数据库过载,可能引发级联故障。
关键区分:穿透是“查不到”导致绕过缓存;击穿是“单个热点”失效导致并发回源;雪崩是“大量 key”同时失效导致整体冲击。

2.2 缓存穿透:定义与典型场景

定义:客户端请求一个缓存和数据库中都不存在的 key,缓存无法命中,每次请求都直接查询数据库。由于数据库中也不存在该记录,查询结果为空,缓存不会回写,导致后续相同请求持续穿透。

典型场景:

  • 恶意攻击:攻击者利用系统漏洞,构造大量不存在的 ID(如负数 ID、超长 ID、随机字符串)发起请求,绕过缓存直接攻击数据库。
  • 业务数据删除:某条数据被从数据库中物理删除,但缓存中对应的 key 未被清理,后续请求查缓存未命中,查数据库也返回空,形成穿透。
  • 参数校验缺失:前端或上游服务未对请求参数做合法性校验,导致非法参数(如不存在的用户 ID)直接穿透到后端。
治理思路:布隆过滤器(Bloom Filter)前置拦截、缓存空对象(Null Object)并设置短过期时间、参数合法性校验。

2.3 缓存击穿:定义与典型场景

定义:一个被高频访问的热点 key 在缓存过期的瞬间,大量并发请求同时发现缓存失效,于是全部回源数据库查询,导致数据库在短时间内承受远超正常水平的读压力。

典型场景:

  • 秒杀活动商品:某爆款商品的详情 key 在秒杀开始瞬间过期,成千上万的用户同时刷新页面,请求全部穿透到数据库。
  • 热门新闻/微博:一条突发新闻的阅读量 key 在缓存失效后,大量用户同时访问,导致数据库查询量瞬间暴涨。
  • 全局配置项:系统全局配置的缓存 key 过期后,所有服务实例同时回源拉取配置,造成配置中心或数据库压力飙升。
注意:缓存击穿与穿透不同,击穿的目标 key 在数据库中是真实存在的,只是缓存恰好失效。治理重点在于“互斥回源”和“热点永不过期”。

2.4 缓存雪崩:定义与典型场景

定义:大量缓存 key 在同一时间窗口内集中失效,或者缓存服务整体不可用(如 Redis 集群宕机),导致所有请求直接落到数据库,引发数据库过载、连接池耗尽,甚至系统整体不可用。

典型场景:

  • 统一过期时间:所有缓存 key 设置了相同的过期时间(如凌晨 0 点),导致在 0 点整大量 key 同时失效,引发雪崩。
  • 缓存服务宕机:Redis 集群因网络分区、内存溢出或主从切换失败而整体不可用,所有请求直接回源数据库。
  • 缓存预热失败:系统重启后,缓存中无数据,大量请求同时涌入数据库进行预热,导致数据库瞬间被打满。
治理思路:过期时间加随机偏移、缓存高可用(Redis Sentinel/Cluster)、本地缓存(L1 Cache)兜底、限流熔断降级。

2.5 三类故障的关系与对比

缓存三类故障对比关系图 缓存穿透 查不到(DB 无数据) 每次请求穿透到 DB 缓存击穿 单个热点 key 失效 高并发回源 DB 缓存雪崩 大量 key 同时失效 整体冲击 DB 共同影响:数据库压力激增 → 连接池耗尽 → 系统不可用 治理目标:保护数据库,保证缓存与 DB 最终一致性 治理策略对比 • 穿透:布隆过滤器 / 空对象缓存 • 击穿:互斥锁 / 热点 key 永不过期 • 雪崩:过期时间随机化 / 缓存高可用 / 限流降级

2.6 典型故障链路示例

以下代码模拟了一个未做任何防护的缓存查询链路,该链路在面对穿透、击穿、雪崩时均会暴露问题:

// 未防护的缓存查询示例(Java 伪代码)
public User getUserById(String userId) {
    // 1. 查缓存
    User user = redis.get("user:" + userId);
    if (user != null) {
        return user;
    }
    // 2. 缓存未命中,查数据库
    user = userDao.selectById(userId);
    if (user != null) {
        // 3. 回写缓存
        redis.set("user:" + userId, user, 3600);
    }
    // 4. 返回结果(若 user 为 null,则穿透)
    return user;
}
问题分析:上述代码未处理“查不到”时的空对象缓存,未对热点 key 做互斥回源,未对过期时间做随机化。在并发场景下,该代码会同时引发穿透、击穿和雪崩风险。

2.7 本章小结

  • 缓存穿透:查不到数据,每次穿透到 DB。治理核心:前置过滤 + 空对象缓存。
  • 缓存击穿:热点 key 失效瞬间,高并发回源 DB。治理核心:互斥锁 + 热点永不过期。
  • 缓存雪崩:大量 key 同时失效或缓存宕机,整体冲击 DB。治理核心:过期时间随机化 + 缓存高可用 + 限流降级。
  • 三类故障并非孤立,穿透可能诱发击穿,击穿积累可能演变为雪崩。在 OpenAPI 契约设计中,错误响应体必须明确区分“数据不存在”“缓存失效”“系统过载”等不同错误码,以便客户端采取差异化重试策略。