在 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 宕机 |
| 核心危害 | 每次请求都打到数据库 | 瞬间并发打到数据库 | 数据库瞬间被压垮 |
| 主要方案 | 缓存空值、布隆过滤器 | 互斥锁、逻辑过期 | 随机过期、多级缓存、高可用 |
五、面试答题思路
在面试中被问到这三个概念时,建议按照以下结构回答:
- 先定义:用一句话说清每个概念的核心特征。
- 再对比:指出三者的关键区别,尤其是穿透与击穿、击穿与雪崩的差异。
- 后方案:针对每个问题给出两到三种解决方案,并说明适用场景。
- 补充实践:结合自己做过的项目,说明实际中是如何组合使用这些方案的。
例如可以这样总结:“缓存穿透是查不存在的数据,用布隆过滤器或缓存空值解决;缓存击穿是热点 key 过期,用互斥锁或逻辑过期解决;缓存雪崩是大量 key 同时失效或 Redis 故障,用随机过期时间、多级缓存和高可用架构解决。实际项目中,这三种方案往往会组合使用,而不是单独依赖某一种。”
理解这三者的本质区别,不仅能帮你通过面试,更能在实际系统设计中做出正确的技术选型。
未经允许不得转载:任鹏个人博客 » Redis 缓存穿透、缓存击穿、缓存雪崩的区别与解决方案

