缓存问题
问题
什么是缓存穿透、击穿、雪崩?分别怎么解决?
答案
一、三大缓存问题对比
| 问题 | 定义 | 原因 | 严重程度 |
|---|---|---|---|
| 缓存穿透 | 查询一个 不存在 的数据 | 恶意攻击或代码 Bug | ⭐⭐⭐ |
| 缓存击穿 | 一个热点 key 过期 | 高并发请求同时穿透 | ⭐⭐⭐⭐ |
| 缓存雪崩 | 大量 key 同时过期 | 集中过期或 Redis 宕机 | ⭐⭐⭐⭐⭐ |
二、缓存穿透
场景:请求 id = -1 的数据,缓存没有,数据库也没有。每次请求都直接打到数据库。
解决方案
方案 1:缓存空值
查数据库 → 不存在 → 缓存 key → null(短过期时间,如 2 分钟)
| 优点 | 缺点 |
|---|---|
| 简单 | 大量不存在的 key 浪费内存 |
方案 2:布隆过滤器(推荐)
布隆过滤器用位数组 + 多个哈希函数判断元素是否存在:
- 说 不存在 → 一定不存在
- 说 存在 → 可能存在(有一定误判率)
三、缓存击穿
场景:某个热点 key(如热门商品)过期的一瞬间,大量请求同时到来,全部打到数据库。
解决方案
方案 1:互斥锁
缓存未命中 → 获取分布式锁 → 只有一个请求查数据库 → 写入缓存 → 释放锁
其他请求 → 等待 → 从缓存读取
方案 2:逻辑过期
不设置实际过期时间,在 value 中存一个逻辑过期时间。
读取时判断:
- 未过期 → 直接返回
- 已过期 → 返回旧数据 + 异步线程去更新缓存
| 方案 | 一致性 | 可用性 |
|---|---|---|
| 互斥锁 | 强一致 | 部分请求需等待 |
| 逻辑过期 | 短暂不一致 | 高可用,不阻塞 |
方案 3:热点 key 永不过期
对已知的热点 key,不设过期时间,在数据变更时主动更新缓存。
四、缓存雪崩
场景:大量 key 在同一时间过期,或者 Redis 实例宕机,导致大量请求涌向数据库。
解决方案
场景 1:大量 key 同时过期
# 添加随机过期时间
expireTime = baseExpire + random(0, 300) # 基础过期 + 0~300 秒随机
场景 2:Redis 宕机
| 方案 | 说明 |
|---|---|
| Redis 高可用 | Sentinel / Cluster |
| 本地缓存(L1) | Caffeine 等进程内缓存 |
| 限流降级 | 保护数据库不被打崩 |
| 熔断器 | 数据库承受不住时直接返回降级响应 |
五、综合防御体系
常见面试问题
Q1: 缓存穿透和缓存击穿的区别?
答案:
- 穿透:查的数据 不存在(数据库也没有)
- 击穿:查的数据 存在但缓存过期了(数据库有)
两者都会导致请求打到数据库,但原因和解决方案不同。
Q2: 布隆过滤器的误判怎么处理?
答案:
误判意味着布隆过滤器说「存在」但实际「不存在」。这种情况请求会穿透到数据库查一次,发现不存在后可以缓存空值。误判率可通过增大位数组或增加哈希函数来降低(通常控制在 1% 以内)。
Q3: 热点 key 如何发现?
答案:
- 业务预判:秒杀商品、热门话题等可预知的热点
- Redis 监控:
redis-cli --hotkeys(4.0+ LFU 模式) - proxy 统计:中间件层统计 key 的访问频率
- 本地缓存兜底:对已知热点 key 加本地缓存