缓存穿透、缓存击穿、缓存雪崩分别是什么?怎么解决?

进阶高频场景题约 4 分钟读完

一句话回答

穿透:查询一个根本不存在的数据,缓存和数据库都没有,每次请求都打到数据库,用缓存空值或布隆过滤器解决。击穿:某个热点 key 过期的瞬间,大量并发请求同时打到数据库,用互斥锁或逻辑过期解决。雪崩:大量 key 同时过期或 Redis 宕机,用随机过期时间、高可用集群和限流降级解决。

详细解析

问题 原因 解决方案
缓存穿透 请求的数据在缓存和数据库里都不存在,常见于恶意请求(如 id = -1) 缓存空值(过期时间设短一些);布隆过滤器提前拦截;接口做参数校验
缓存击穿 单个热点 key 过期,大量并发请求同时查库 互斥锁(只让一个请求重建缓存);逻辑过期(不设 TTL,发现过期后异步刷新);热点数据不过期
缓存雪崩 大量 key 同时过期,或 Redis 整体不可用 过期时间加随机值;主从 + 哨兵或集群保证高可用;多级缓存;限流、降级、熔断

代码示例

下面用互斥锁防击穿,顺带处理穿透和雪崩(Node.js + ioredis):

JavaScript
async function getProduct(id) {
  const key = `product:${id}`
  const cached = await redis.get(key)
  // 空字符串表示缓存的"数据不存在"
  if (cached !== null) return cached === '' ? null : JSON.parse(cached)

  // SET NX EX:原子地加锁并设置过期时间,防止进程崩溃后锁一直不释放
  const lockKey = `lock:${key}`
  const locked = await redis.set(lockKey, '1', 'EX', 10, 'NX')
  if (!locked) {
    await sleep(50)
    return getProduct(id) // 没抢到锁,稍后重试
  }

  try {
    // 双重检查:等锁期间别的请求可能已经重建了缓存
    const again = await redis.get(key)
    if (again !== null) return again === '' ? null : JSON.parse(again)

    const product = await db.findProduct(id)
    if (product) {
      // 过期时间加随机值,防止大量 key 同时过期
      await redis.set(key, JSON.stringify(product), 'EX', 3600 + Math.floor(Math.random() * 600))
    } else {
      await redis.set(key, '', 'EX', 60) // 缓存空值,防止穿透
    }
    return product
  } finally {
    await redis.del(lockKey)
  }
}

面试官可能追问

布隆过滤器的原理和缺点?

它由一个位数组和多个哈希函数组成。写入时用多个哈希函数算出几个位置并置为 1;查询时只要有一个位置是 0,就一定不存在;全是 1 只能说明可能存在。缺点是存在误判,而且标准的布隆过滤器不支持删除(可以用计数布隆过滤器)。

互斥锁和逻辑过期怎么选?

互斥锁保证数据一致,但没抢到锁的请求要等待,接口会变慢;逻辑过期性能好,过期后先返回旧数据、再异步重建,但会短时间返回旧数据。看业务更看重一致性还是可用性。

上面代码里的锁有什么问题?

释放锁时直接 del,可能误删别人的锁:如果业务执行时间超过了锁的 10 秒过期时间,锁已经被别的请求拿到,这时删掉的就是别人的锁。正确做法是加锁时写入唯一值,释放时用 Lua 脚本判断"是自己的锁才删除",保证判断和删除是原子操作。

易错点

  • 击穿针对一个热点 key,雪崩针对一大批 key 或整个缓存层,不要混淆
  • 缓存空值要设较短的过期时间,否则数据真正写入后,在空值过期前都查不到

AI 模拟面试官

用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。