面试题:在 Redis 中,什么是 “缓存穿透” “缓存击穿” “缓存雪崩”?分别有哪些解决方案?

在使用 Redis 作为缓存时,我们极大地提升了应用的性能和响应速度。然而,不当的使用或极端情况可能导致缓存失效,大量请求直接冲击后端数据库,引发系统故障。其中,最著名的三个问题就是 “缓存穿透”、“缓存击穿” 和“缓存雪崩”。

一、缓存穿透 (Cache Penetration)

1. 问题定义

缓存穿透指的是客户端请求查询一个在缓存和数据库中都根本不存在的数据。

由于缓存中没有(缓存未命中),请求会直接涌向数据库。如果这类请求量巨大(例如,恶意攻击者构造大量非法 ID),数据库的压力会骤增,最终可能导致服务崩溃。它的核心是 “查询不存在的数据”。

典型场景

  • 恶意用户使用不存在的 userId (如 -1 或随机字符串) 频繁查询用户信息。

  • 业务逻辑错误,调用方生成了不合法的查询参数。

2. 解决方案

  • 缓存空值 (Cache Null Values)

  • 原理

    当数据库查询确认某个数据不存在时,仍然将这个 “空结果” 缓存起来,但为其设置一个较短的过期时间(例如 60 秒)。

  • 优点

    实现简单,能有效阻挡在短期内对同一无效 key 的重复攻击。

  • 缺点

    会消耗一定的缓存空间;可能存在短期的数据不一致(如果在这期间数据库中又创建了该数据)。

  • 布隆过滤器 (Bloom Filter)

  • 预加载

    将数据库中所有存在的 key 预先加载到布隆过滤器中。

  • 请求过滤

    当请求到来时,先去布隆过滤器查询。如果过滤器判断 key 不存在,则直接拒绝请求,根本不会去查缓存和数据库。如果判断可能存在,再放行请求去查缓存。

  • 原理

    在客户端和缓存之间设置一道屏障。布隆过滤器是一种高效的概率型数据结构,用于判断一个元素是否可能存在于一个集合中。

  • 优点

    内存占用极小,过滤效率高,能拦截绝大部分无效请求。

  • 缺点

    存在一定的误判率(一个不存在的 key 可能会被误判为存在);实现相对复杂。

  • 接口层参数校验 (Parameter Validation)

  • 原理

    在应用的最外层(如 Controller 或 API Gateway)对请求参数进行合法性校验。例如,用户 ID 是否为正整数、请求格式是否正确等。不合法的请求直接返回错误,避免后续查询。

  • 优点

    简单直接,能拦截明显的非法请求,是开发的基本素养。

  • 缺点

    无法拦截那些 “看起来合法但实际不存在” 的 key。

二、缓存击穿 (Cache Breakdown)

1. 问题定义

缓存击穿(也称 “热点 key 问题”)指的是某一个访问极其频繁的热点 Key,在它过期失效的瞬间,海量的并发请求同时涌入,越过缓存直接打到数据库上,导致数据库压力瞬间飙升。

它的核心是 “单个热点 Key 过期”。

典型场景

  • 某款爆款商品的详情页缓存。

  • 微博热搜榜第一条内容的缓存。

2. 解决方案

  • 互斥锁 (Mutex Lock)

  • 原理

    当缓存失效时,不是让所有请求都去查数据库,而是只让第一个请求去查,并加一个互斥锁(如 Redis 的 SETNX)。其他请求则进入等待状态或直接返回一个友好提示。当第一个请求将数据写回缓存后,再释放锁,其他请求便可以从缓存中获取数据。

  • 优点

    严格保证了数据一致性,只让一个请求重建缓存,有效保护数据库。

  • 缺点

    实现逻辑相对复杂,可能会因为引入锁而降低一部分吞吐量。

  • 热点数据永不过期 (Logical Expiration)

  • 如果未过期,直接返回数据。

  • 如果已过期,启动一个后台异步线程去更新缓存,而当前请求则直接返回旧的(但仍可用)数据。

  • 原理

    对于极度热点的 Key,物理上不设置过期时间(EXPIRE),而是在 value 中存储一个逻辑上的过期时间戳。当访问时,由代码逻辑判断其是否 “逻辑过期”。

  • 优点

    避免了并发重建缓存的问题,用户体验平滑。

  • 缺点

    数据不是强一致性的;实现方案更复杂。

三、缓存雪崩 (Cache Avalanche)

1. 问题定义

缓存雪崩指的是在某一个时间段内,缓存集中地、大规模地失效,导致所有请求都直接涌向数据库,如同雪崩一样,最终压垮数据库。

导致雪崩的两种主要情况

  • 大量 Key 同时过期

    比如在系统启动时,将大量数据同时加载到缓存并设置了相同的过期时间。

  • Redis 实例宕机

    Redis 集群发生故障,无法提供服务,所有请求自然就落到了数据库头上。

2. 解决方案

  • 过期时间加随机值 (Randomize Expiration)

  • 原理

    在设置 Key 的过期时间时,不再使用固定的值,而是在基础时间上增加一个随机数。例如,原本是 EXPIRE key 3600,现在改为 EXPIRE key (3600 + rand(0, 300))

  • 优点

    简单高效,能有效打散 Key 的过期时间,避免集中失效。

  • 构建高可用的 Redis 集群

  • 原理

    通过主从复制 + 哨兵(Sentinel)或直接使用 Redis Cluster 模式,避免单点故障。如果主节点宕机,从节点可以迅速顶上,保证缓存服务持续可用。

  • 优点

    从根本上解决了 “Redis 挂了” 的问题,是生产环境的标配。

  • 缺点

    架构复杂度更高,运维成本增加。

  • 服务熔断与限流降级 (Hystrix, Sentinel)

  • 原理

    在应用层增加一层保护。当检测到数据库压力过大或请求响应过慢时,启动限流(只放行一部分请求到数据库)或熔断(在一段时间内直接拒绝服务,返回友好提示或兜底数据),给数据库喘息之机。

  • 优点

    是应对各种意外情况的终极 “保险丝”,保证系统不会彻底崩溃。

  • 缺点

    需要引入额外的组件和配置。

总结

问题类型 核心原因 关键解决方案
缓存穿透 查询一个绝对不存在的数据,导致请求永远穿过缓存。 1. 缓存空对象
2. 布隆过滤器
缓存击穿 某一个超级热点Key在过期瞬间,被海量并发请求访问。 1. 互斥锁/分布式锁
2. 逻辑过期(热点数据永不过期)
缓存雪崩 1. 大量Key在同一时间集中过期
2. Redis服务本身宕机。
1. 随机化过期时间
2. 高可用集群(主从/哨兵/Cluster)
3. 服务限流与熔断降级