校招必问:Redis 如何解决缓存穿透、雪崩、击穿三大难题
🌪️ 前言:从“缓存真香”到“线上事故”
很多同学做项目时,觉得引入 Redis 做缓存就是“性能救星”:查询先走 Redis,没有再查数据库,速度飞起!直到上线后遇到诡异问题:
-
突然大量请求把数据库打挂,CPU 爆了;
-
某热门商品页突然全白,日志里全是 “DB 连接超时”;
-
半夜被报警叫醒:“缓存穿透,请求直接冲垮 DB!”
这就是缓存三大坑:穿透、雪崩、击穿。今天用校招面试官最爱考的角度,拆解 Redis 如何优雅解决它们,还附Lua 脚本实战!
🔍 问题现象:缓存的“灵异事件”
1. 缓存穿透:“查不存在的数据,DB 被打穿”
-
场景:用户恶意请求一个不存在的商品 ID(如
product:999999),Redis 中没有,每次都穿透到 DB 查询。 -
结果:大量请求直接打 DB,DB 压力暴增,甚至宕机。
2. 缓存雪崩:“大量缓存同时失效,DB 被瞬间压垮”
-
场景:电商大促前,运营批量设置了 10 万商品的缓存有效期为 1 小时,整点(如 20:00)所有缓存同时过期。
-
结果:20:00 整,所有请求瞬间穿透到 DB,DB 并发量骤增,直接被压垮。
3. 缓存击穿:“热点 Key 过期瞬间,DB 被瞬间冲击”
-
场景:某明星带货,某商品(
product:10086)的缓存恰好在流量峰值期过期,此时百万请求同时涌入。 -
结果:缓存失效,百万请求直接冲 DB,DB 瞬间被打满,服务雪崩。
📚 你必须懂的:缓存的底层逻辑
缓存的核心是“空间换时间”,但分布式场景下,缓存和 DB 的配合需要更精细的设计。
1. 缓存的工作流程(经典流程)
-
读操作:先查 Redis → 存在则返回;不存在则查 DB → DB 返回后,写入 Redis 再返回。
-
写操作:先改 DB → 再删 Redis(或更新 Redis)。
2. 缓存的“有效期(TTL)”机制
Redis 的 Key 可以设置过期时间(如 EX 3600),到期后自动删除。但如果大量 Key 同时过期,就会引发雪崩;如果热点 Key 过期,就会引发击穿。
🛠️ 缓存三大问题的解决方案
1. 缓存穿透:“查不存在的数据,DB 被打穿”
问题本质:请求的数据缓存和 DB 都不存在,缓存永远“挡不住”,请求直接打 DB。
解决方案 1:缓存空对象(简单但耗内存)
-
逻辑:当 DB 查询不到数据时,也往 Redis 中写一个“空值”(如
""或null),并设置较短的 TTL(如 5 分钟)。 -
优点:实现简单,维护方便。
-
缺点:消耗内存;存在短期不一致(比如 DB 后来新增了数据,Redis 里的空值还没过期,会返回空)。
解决方案 2:布隆过滤器(高效拦截不存在的请求)
-
逻辑:布隆过滤器是一种概率型数据结构,特点是:不存在的元素一定不存在,存在的元素可能存在。
-
实现:把所有可能存在的 Key 提前放入布隆过滤器。请求时,先过布隆过滤器:
-
如果过滤器说“不存在”,直接返回(DB 一定没有);
-
如果过滤器说“存在”,再查 Redis/DB。
-
2. 缓存雪崩:“大量缓存同时失效,DB 被瞬间压垮”
问题本质:大量缓存 Key 同时过期(或 Redis 宕机),请求全部穿透到 DB,DB 压力暴增。
解决方案 1:给不同 Key 加随机 TTL(避免同时过期)
-
逻辑:设置缓存 TTL 时,在原基础上加一个随机值(如
TTL = 3600 + rand(0, 600)),让 Key 的过期时间分散。 -
优点:实现简单,有效避免“集体过期”。
解决方案 2:提高 Redis 高可用性(哨兵/集群)
-
逻辑:用 Redis 哨兵(Sentinel) 或 Redis Cluster 保证 Redis 服务不宕机。即使主节点挂了,从节点/集群节点能快速接管,避免缓存整体失效。
解决方案 3:缓存降级 + 限流
-
逻辑:当 Redis 故障或 DB 压力过大时,降级为非缓存逻辑(如返回默认值、静态页面);同时通过限流(如 Sentinel、Guava RateLimiter)限制请求量,保护 DB。
解决方案 4:多级缓存(大型互联网应用)
-
逻辑:客户端(浏览器)+ 网关(Nginx)+ 应用层(本地缓存)+ Redis,多层缓存分散压力。即使 Redis 失效,本地缓存也能挡一部分请求。
3. 缓存击穿:“热点 Key 过期瞬间,DB 被瞬间冲击”
问题本质:一个高并发的热点 Key 突然过期,大量请求同时穿透到 DB,DB 被打满。
解决方案 1:互斥锁(分布式锁)
-
逻辑:当缓存失效时,只允许一个线程去重建缓存(查 DB + 写 Redis),其他线程等待或返回旧值。
-
实现:用 Redis 的
SETNX(Set if Not Exists)命令实现分布式锁。
解决方案 2:逻辑过期(不设置 TTL,永久有效)
-
逻辑:缓存 Key 不设置过期时间,但存储一个“逻辑过期时间”(如
expire_time字段)。业务层查询时,判断逻辑时间是否过期:-
没过期:直接返回缓存值;
-
过期了:启动异步线程去重建缓存,当前线程返回旧值(或默认值)。
-
🎯 一句话总结(面试必背)
-
缓存穿透:查不存在的数据 → 用缓存空对象或布隆过滤器拦截,避免请求打 DB。
-
缓存雪崩:大量缓存同时失效 → 用随机 TTL、高可用架构、降级限流、多级缓存分散压力。
-
缓存击穿:热点 Key 过期 → 用分布式锁(互斥)或逻辑过期(异步重建),保护 DB 不被瞬间冲击。
Redis 解决这三大问题的核心是“原子化操作(Lua 脚本)+ 分层防护(空对象、布隆、锁、随机 TTL)”,结合工具类封装,可大幅提升系统稳定性!
📌 面试加分项
-
能说出布隆过滤器的“误判率”(哈希函数数越多、位数组越大,误判率越低,但内存消耗越高)。
-
能对比“互斥锁”和“逻辑过期”的适用场景(互斥锁适合缓存重建快的场景;逻辑过期适合缓存重建慢、允许短暂不一致的场景)。
-
能画出多级缓存架构图(客户端 → Nginx → 本地缓存 → Redis → DB),并解释每一层的作用。
更多推荐




所有评论(0)