Redis 缓存穿透、缓存击穿、缓存雪崩的区别与解决方案

在 Redis 面试中,缓存穿透、缓存击穿、缓存雪崩是三个高频考点。它们看似相近,实则对应不同的问题场景,解决方案也各有侧重。本文将从定义、产生原因、危害以及应对策略四个维度,逐一拆解这三个概念,帮助你在面试中从容作答,也能在实际工作中快速定位问题。

一、缓存穿透

1.1 什么是缓存穿透

缓存穿透是指客户端请求的数据在缓存和数据库中都不存在。由于缓存中查不到,请求就会直接打到数据库;而数据库中也没有这条数据,于是无法回写缓存。当下一次同样的请求到来时,又会重复上述过程,导致每次请求都绕过缓存直击数据库。

1.2 产生原因

  • 恶意攻击:攻击者故意请求大量不存在的 ID,例如 id=-1 或随机生成的超大 ID。
  • 业务逻辑缺陷:查询条件本身就不可能有对应数据,但代码没有做前置校验。

1.3 危害

如果短时间内有大量不存在的 key 请求涌入,数据库压力骤增,可能导致数据库连接耗尽甚至宕机。由于缓存层完全没有起到保护作用,这种攻击成本极低,危害却很大。

1.4 解决方案

方案一:缓存空值

对于数据库中查不到的数据,仍然在缓存中写入一个空值(如 null 或特殊标记),并设置较短的过期时间(例如 60 秒)。这样后续相同请求会直接命中缓存,不会打到数据库。

String value = redis.get(key);
if (value != null) {
    return value.equals("NULL") ? null : value;
}
// 查数据库
Object dbValue = db.query(key);
if (dbValue == null) {
    redis.setex(key, 60, "NULL"); // 缓存空值,过期时间不宜过长
    return null;
}
redis.setex(key, 3600, dbValue);
return dbValue;

方案二:布隆过滤器

在缓存层之前加一层布隆过滤器,将所有可能存在的 key 预先存入过滤器。请求到来时,先用布隆过滤器判断 key 是否存在:如果不存在,直接返回;如果存在,再查缓存和数据库。

布隆过滤器的优点是空间效率高、查询速度快;缺点是有一定的误判率(可能把不存在的判定为存在),且不支持删除操作。因此它通常作为第一道防线,配合缓存空值使用效果更好。

方案三:参数校验

在接口层对请求参数做基础校验,例如 ID 必须为正整数、分页大小不能超过阈值等,从源头拦截明显非法的请求。

二、缓存击穿

2.1 什么是缓存击穿

缓存击穿是指某个热点 key 在缓存中过期的瞬间,大量并发请求同时涌向数据库,导致数据库瞬时压力过大。注意,这个 key 是真实存在的,只是恰好过期了。

2.2 产生原因

  • 热点 key 设置了过期时间,且过期时间到达时恰好有高并发访问。
  • 缓存重建耗时较长,在重建完成之前,所有请求都落在数据库上。

2.3 与缓存穿透的区别

缓存穿透是数据根本不存在,每次请求都绕过缓存;缓存击穿是数据存在,只是缓存过期导致短时间内请求直接打到数据库。前者是“持续性伤害”,后者是“瞬间爆发”。

2.4 解决方案

方案一:互斥锁

当缓存失效时,不是所有线程都去查数据库,而是只允许一个线程去重建缓存,其他线程等待或重试。

String value = redis.get(key);
if (value == null) {
    String lockKey = "lock:" + key;
    // 尝试获取分布式锁
    if (redis.setnx(lockKey, "1", 10)) {
        try {
            value = db.query(key);
            redis.setex(key, 3600, value);
        } finally {
            redis.del(lockKey);
        }
    } else {
        // 其他线程短暂等待后重试
        Thread.sleep(50);
        return get(key);
    }
}
return value;

方案二:逻辑过期

不给热点 key 设置物理过期时间,而是在 value 中额外维护一个逻辑过期时间字段。当发现逻辑过期时,异步启动一个线程去重建缓存,当前请求仍然返回旧数据。这样保证了可用性,但会有一段时间返回旧数据。

方案三:热点数据永不过期

对于极少数确认的热点 key,可以设置为永不过期,通过后台定时任务或事件驱动的方式主动更新缓存。

三、缓存雪崩

3.1 什么是缓存雪崩

缓存雪崩是指在某一时刻,大量缓存 key 同时过期,或者 Redis 服务本身宕机,导致所有请求都涌向数据库,数据库压力骤增,甚至引发连锁反应导致整个系统崩溃。

3.2 产生原因

  • 大量 key 设置了相同的过期时间,例如在凌晨统一刷新缓存。
  • Redis 集群故障或网络抖动,导致缓存层整体不可用。
  • 缓存预热不足,系统启动时大量请求直接打到数据库。

3.3 与缓存击穿的区别

缓存击穿针对的是单个热点 key,缓存雪崩针对的是大量 key 或整个缓存层。击穿是“点”的问题,雪崩是“面”的问题。

3.4 解决方案

方案一:过期时间加随机值

在设置过期时间时,加上一个随机偏移量,避免大量 key 在同一时刻过期。

int expireTime = 3600 + new Random().nextInt(600); // 3600~4200 秒
redis.setex(key, expireTime, value);

方案二:多级缓存

在 Redis 之前再加一层本地缓存(如 Caffeine、Guava Cache),即使 Redis 整体不可用,本地缓存仍能抵挡一部分流量。

方案三:Redis 高可用

通过主从复制、哨兵模式或 Redis Cluster 保证缓存层的高可用性,避免单点故障导致整个缓存层不可用。

方案四:限流与降级

在数据库之前加入限流组件(如 Sentinel、Hystrix),当请求量超过阈值时直接返回降级响应,保护数据库不被压垮。

方案五:缓存预热

系统上线前,提前将热点数据加载到缓存中,避免启动瞬间大量请求打到数据库。

四、三者对比总结

维度 缓存穿透 缓存击穿 缓存雪崩
数据是否存在 不存在 存在 存在
问题范围 单个或多个不存在的 key 单个热点 key 大量 key 或整个缓存层
触发原因 恶意请求或逻辑缺陷 热点 key 过期 大量 key 同时过期或 Redis 宕机
核心危害 每次请求都打到数据库 瞬间并发打到数据库 数据库瞬间被压垮
主要方案 缓存空值、布隆过滤器 互斥锁、逻辑过期 随机过期、多级缓存、高可用

五、面试答题思路

在面试中被问到这三个概念时,建议按照以下结构回答:

  1. 先定义:用一句话说清每个概念的核心特征。
  2. 再对比:指出三者的关键区别,尤其是穿透与击穿、击穿与雪崩的差异。
  3. 后方案:针对每个问题给出两到三种解决方案,并说明适用场景。
  4. 补充实践:结合自己做过的项目,说明实际中是如何组合使用这些方案的。

例如可以这样总结:“缓存穿透是查不存在的数据,用布隆过滤器或缓存空值解决;缓存击穿是热点 key 过期,用互斥锁或逻辑过期解决;缓存雪崩是大量 key 同时失效或 Redis 故障,用随机过期时间、多级缓存和高可用架构解决。实际项目中,这三种方案往往会组合使用,而不是单独依赖某一种。”

理解这三者的本质区别,不仅能帮你通过面试,更能在实际系统设计中做出正确的技术选型。

未经允许不得转载:任鹏个人博客 » Redis 缓存穿透、缓存击穿、缓存雪崩的区别与解决方案

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏