缓存穿透、击穿、雪崩:三个词分不清,方案就会用错

#中间件#面试八股

缓存穿透、击穿、雪崩:三个词分不清,方案就会用错

这三个词都在说"缓存扛不住,请求打到 DB 了",但成因完全不同,方案也不能互换。 面试里最常被扣分的答案,是把三者混成一句"加锁 + 布隆过滤器 + 过期时间打散"。

1. 一张表先分清

名词触发条件请求特征关键区别
穿透查的数据根本不存在大量请求 key 不存在,缓存永远不命中请求"绕过"了缓存
击穿某个热点 key 过期单个 key 的并发瞬间打到 DB单点失效
雪崩大量 key 同时过期 或缓存整体不可用全站请求同时打到 DB面失效

2. 穿透:请求的数据不存在

请求 user_id=99999999(不存在)
  → 缓存未命中
  → 查 DB,查不到
  → 不写缓存(没数据可写)
  → 下一个同样的请求,重复上面

攻击者可以用不存在的 id 打穿缓存直击 DB——这既是性能问题也是安全问题。

方案

  1. 缓存空值:查不到也写一个短 TTL 的空标记(如 60s)
    key=user:99999999  value=""  ttl=60s
    
    优点:实现最简;缺点:恶意构造大量随机 key 会撑爆缓存(要配合限流)。
  2. 布隆过滤器:写入数据时把 key 放进布隆过滤器,查询前先判断"可能存在"
    布隆说不存在 → 直接返回(100% 准确)
    布隆说存在   → 再查缓存/DB(可能误判)
    
    注意:只能判断"一定不存在",不能判断"一定存在";删除需要计数布隆或定期重建。
  3. 参数校验 + 限流:id 范围、格式校验,前置网关限流——最简单也最容易被忽略。

3. 击穿:热点 key 过期

某个 key(比如首页 banner、秒杀商品)QPS 极高,一到过期瞬间, 所有并发请求同时 miss → 同时查 DB。

方案

  1. 互斥锁(只让一个线程回源)
    String v = cache.get(key);
    if (v == null) {
        if (lock.tryLock()) {          // 只放一个请求去查 DB
            try {
                v = db.query(key);
                cache.set(key, v, ttl);
            } finally { lock.unlock(); }
        } else {
            Thread.sleep(50);          // 其它请求稍等再读缓存
            return cache.get(key);
        }
    }
    
    注意:锁的粒度按 key,不要用一把全局锁。
  2. 逻辑过期(不设 TTL,值里带过期时间)
    读取时判断 value.expireAt 是否过期
      未过期 → 直接返回
      已过期 → 返回旧值,同时异步起一个线程刷新
    
    优点:永不阻塞读;缺点:会短暂返回旧数据(对一致性要求低的场景最合适)。
  3. 热点 key 永不过期 + 主动更新:适合配置类、banner 类数据。

4. 雪崩:大面积同时失效

两种成因:

  • 同一时刻批量写入的 key 用了相同 TTL → 到期时间被对齐;
  • Redis 实例宕机/网络分区 → 缓存整体不可用。

方案

  1. TTL 加随机抖动
    int ttl = baseTtl + ThreadLocalRandom.current().nextInt(0, 300);
    
    把"同时过期"打散成"陆续过期"。
  2. 缓存高可用:主从 + 哨兵 / Cluster,避免单点;多级缓存(本地 Caffeine + Redis)。
  3. 限流 + 降级:缓存挂了时,网关限流保护 DB,非核心功能直接降级返回兜底数据。
  4. 预热:重启/发布后先加载热点数据,避免冷启动瞬间全穿透。

5. 三者可以叠加出现

真实故障往往是组合:热点 key 过期(击穿)→ 大量请求查 DB → DB 变慢 → 更多 key 超时未写缓存 → 雪崩。

所以完整方案是一套组合拳:

接入层:参数校验 + 限流(防穿透的第一道闸)
缓存层:空值缓存 + 布隆过滤器(防穿透)
       互斥锁 / 逻辑过期(防击穿)
       TTL 抖动 + 高可用 + 预热(防雪崩)
兜底层:降级预案 + DB 连接池保护(防雪崩的最后一环)

6. 常被追问的两个点

Q:为什么不用"永不过期"解决一切? 内存有限,且数据会变;永不过期意味着你要自己维护更新逻辑,复杂度转移到业务侧。

Q:布隆过滤器能删除元素吗? 标准布隆不能(删除会破坏其他元素的判定)。要删除用计数布隆(Counting Bloom), 或者用"定期重建 + 双缓冲"切换。

7. 一个小结口诀

  • 穿透:查不存在 → 空值缓存 / 布隆 / 校验;
  • 击穿:热点过期 → 互斥回源 / 逻辑过期;
  • 雪崩:大面积失效 → TTL 抖动 / 高可用 / 限流降级。

面试时先讲清"三者成因不同",再分别给方案,最后补一句"真实故障常是组合,所以要做组合防御"—— 这比背三个名词更能体现你处理过线上问题。

笔记加载中…