什么是缓存击穿、缓存穿透、缓存雪崩?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官提出这个问题,主要是想考察:

  1. 基础概念理解:你是否能清晰区分这三个常见的缓存问题。
  2. 问题诊断能力:你是否理解每个问题发生的场景、原因与后果
  3. 实战解决方案:面对这些典型的生产环境问题,你是否掌握成熟、有效的预防与解决策略
  4. 系统设计思维:通过你的回答,能看出你是否具备防御性编程保障系统高可用的意识,而不仅仅是背诵概念。

核心答案

它们是高并发场景下,缓存系统失效时引发的三种典型问题:

  • 缓存击穿:一个热点 Key在过期瞬间,海量请求直接打向数据库,导致数据库压力激增。
  • 缓存穿透:请求的数据在缓存和数据库中都不存在(如恶意请求不存在的 ID),导致每次请求都穿透到数据库。
  • 缓存雪崩:在某一时刻,大量缓存 Key 集中过期缓存服务宕机,导致所有请求涌向数据库,可能引发数据库崩溃。

深度解析

原理、场景与解决方案

1. 缓存击穿 (Cache Breakdown)

  • 原理:就像一道被频繁访问的 “热点门” 突然锁坏了,所有人都挤向另一道门。本质是并发访问缓存失效在热点数据上的碰撞。

  • 场景与影响:例如,首页爆款商品信息缓存过期,瞬间被上万 QPS 请求,数据库连接池可能被打满。

  • 解决方案与最佳实践

    1. 永不过期 + 异步更新:对极热点 Key 不设过期时间,而是由后台任务或数据变更时异步更新缓存。这是最直接有效的方法。

    2. 互斥锁:当缓存失效时,不立即去查数据库,而是先尝试获取一个分布式锁(如使用 Redis 的 SETNX)。只有拿到锁的线程去查询数据库并回填缓存,其他线程等待或重试缓存。

      // 伪代码示例:互斥锁思路
      public Data getData(String key) {
          Data data = cache.get(key);
          if (data == null) { // 缓存失效
              String lockKey = "lock:" + key;
              if (tryLock(lockKey)) { // 尝试获取分布式锁
                  try {
                      // 双重检查,防止其他线程已经更新了缓存
                      data = cache.get(key);
                      if (data == null) {
                          data = db.get(key); // 查数据库
                          cache.set(key, data, ttl);
                      }
                  } finally {
                      releaseLock(lockKey);
                  }
              } else {
                  // 未获取到锁,等待一小段时间后重试缓存
                  Thread.sleep(50);
                  return getData(key);
              }
          }
          return data;
      }
      
    3. 逻辑过期:在缓存 Value 中封装一个逻辑过期时间。当发现物理缓存未过期但逻辑时间已过期时,另启线程更新缓存,当前线程返回旧数据。这保证了服务的可用性。

2. 缓存穿透

  • 原理:攻击者利用系统中不存在的 Key 发起大量请求,缓存形同虚设,请求每次都 “穿透” 到数据库。
  • 场景与影响:恶意爬虫、攻击者遍历 ID(如 id=-1 或极大值),可能导致数据库被无意义查询拖垮。
  • 解决方案与最佳实践
    1. 参数校验:在 API 网关或 Controller 层对请求参数做合法性校验(如ID范围、格式),拦截非法请求。
    2. 缓存空对象:当数据库也查不到时,将一个空值或特殊标记(如 "NULL")写入缓存,并设置一个较短的过期时间(如5分钟)。后续请求将命中这个空缓存。
      • 注意:需要防范大量不同Key导致缓存被无用空值占满的风险,可考虑设置较短的 TTL 或使用不同的缓存实例。
    3. 布隆过滤器 (Bloom Filter):这是最经典的防御手段。在查询缓存前,先用布隆过滤器判断 Key 是否存在。如果布隆过滤器说 “不存在”,则一定不存在,直接返回空,避免对缓存和数据库的访问。
      • 工作机制:一个大的位数组和多个哈希函数。添加元素时,用多个哈希函数算出多个位置并置1。判断时,只要有一个位置为 0,则元素肯定不存在;全为 1,则元素可能存在(有极小的误判率)。

3. 缓存雪崩

  • 原理:大量缓存在同一时间点失效,或 Redis 集群宕机,产生了“雪崩效应”,所有请求像雪崩一样压垮数据库。

  • 场景与影响:① 业务初期设置缓存 TTL 相同(如都设为 24小时),在第二天同一时间点集体失效。② Redis 主节点宕机,从节点未能及时切换。

  • 解决方案与最佳实践

    1. 错峰过期:为缓存 Key 的 TTL 设置一个随机值(如基础时间+随机分钟数),避免同时失效。

      // 示例:基础过期时间30分钟,加上0-10分钟的随机值
      int baseTtl = 30 * 60; // 30分钟
      int randomTtl = new Random().nextInt(10 * 60); // 0-10分钟随机
      cache.set(key, value, baseTtl + randomTtl);
      
    2. 高可用架构:使用 Redis Sentinel 或 Cluster 实现集群高可用,避免单点故障导致的全盘崩溃。

    3. 服务降级与熔断:在应用层引入熔断器(如 Hystrix, Sentinel)。当数据库访问异常激增时,快速失败(返回默认值、兜底数据),保护数据库,并给缓存恢复留出时间。

    4. 多级缓存:构建本地缓存(如 Caffeine) + 分布式缓存(Redis)的多级架构。即使 Redis 挂掉,本地缓存还能支撑一部分流量,为故障恢复赢得时间。

三者对比与关联

问题核心原因影响范围关键解决方案
缓存击穿单个热点 Key失效局部数据库压力激增永不过期、互斥锁、逻辑过期
缓存穿透数据根本不存在数据库被无效查询攻击参数校验、缓存空值、布隆过滤器
缓存雪崩大量 Key 同时失效或服务宕机全局数据库可能崩溃错峰过期、集群高可用、服务降级

它们之间也存在关联和叠加可能:大量不存在的 Key 攻击(穿透)可能瞬间占满连接池,而此时若有热点 Key 失效(击穿),或触发其他 Key 连锁失效,就可能演变为一场“雪崩”。

总结

缓存击穿、穿透、雪崩是保障缓存系统高可用的核心命题,其应对思路体现了系统设计中冗余、隔离、熔断、降级等重要原则,理解并解决它们是构建高性能、高可用系统的必备能力。