Redis 缓存三大问题
内容:概念、原因、解决方案、实战建议。
Redis 缓存三大问题:
缓存穿透、缓存击穿、缓存雪崩
请求的数据在缓存和数据库中都不存在,每次请求都会直接打到数据库。
- 请求 不存在的 ID
- 被恶意攻击(大量随机 key)
- 接口未做参数校验
GET user:99999999
-> Redis 没有
-> MySQL 也没有
-> 每次都查 DB
- DB 被持续打爆
- 缓存形同虚设
key 不存在 -> 缓存一个空对象/null
TTL 设置短一点(如 30~60s)
- ✅ 优点:简单有效
- ⚠️ 注意:必须设置过期时间
- 请求前先判断 key 是否可能存在
- 不存在 -> 直接拒绝,不查缓存/DB
📌 适合:
- 海量 key
- 恶意攻击场景
- ID <= 0 直接返回
- 非法参数拦截
缓存穿透是访问不存在数据导致 DB 压力,解决方案是缓存空值或使用 Bloom Filter(布隆过滤器)。
某一个热点 key 过期瞬间,大量并发请求同时打到数据库。
- 热点商品
- 热点配置
- 热点活动
某一秒:
key 过期
10000 个请求同时访问
-> DB 被瞬间击穿
- 数据库瞬时压力暴涨
- 服务 RT 飙升
第一个请求:
加锁 -> 查 DB -> 回填缓存
其他请求:
等锁 / 返回旧值
📌 实现方式:
- Redis SETNX
- Redisson 分布式锁
value = {
data: xxx,
expireTime: 2025-01-01 12:00:00
}
- 过期了也先返回旧值
- 后台异步更新缓存
📌 适合:
- 读多写少
- 允许短时间脏数据
- 只人工或定时刷新
缓存击穿是热点 key 失效导致并发打 DB,解决方案是互斥锁或逻辑过期。
大量 key 在同一时间过期或 Redis 整体不可用,导致所有请求涌向数据库。
- 大量 key 使用相同 TTL
- Redis 宕机
- Redis 重启、主从切换
- DB 被打死
- 整个系统雪崩式故障
TTL = 300 + random(0~60)
避免大量 key 同时过期。
本地缓存 -> Redis -> DB
Redis 挂了仍有兜底。
- 主从复制
- Sentinel
- Cluster
- QPS 限制
- 降级返回默认值
缓存雪崩是大量缓存失效或 Redis 不可用导致 DB 被压垮,解决方案是 TTL 随机化和高可用架构。
| 问题 | 核心原因 | 影响范围 | 解决重点 |
|---|---|---|---|
| 缓存穿透 | 数据不存在 | 持续 | 空值缓存 / Bloom |
| 缓存击穿 | 热点 key 过期 | 瞬时 | 锁 / 逻辑过期 |
| 缓存雪崩 | 大量 key 同时失效 | 全局 | 随机 TTL / 高可用 |
👉 是否存在、是否热点、是否大面积。
👉 缓存击穿(热点 key)。
- 穿透:缓存空值 + Bloom
- 击穿:互斥锁
- 雪崩:TTL 随机 + Redis 高可用
结论:
仅靠“计划任务定期更新缓存”,不能彻底解决 Redis 缓存三大问题,只能缓解其中一部分。
下面按 穿透 / 击穿 / 雪崩,逐一给出可落地的结论。
总览(先记住):
| 问题 | 定期任务能否解决 | 结论 |
|---|---|---|
| 缓存穿透 | ❌ 不能 | 数据本就不存在 |
| 缓存击穿 | ⚠️ 部分缓解 | 热点 key 仍有风险 |
| 缓存雪崩 | ⚠️ 只能缓解一类 | 无法应对 Redis 不可用 |
回顾定义:
请求的数据在缓存和数据库中都不存在
定期任务的问题在哪?
- 定时任务 只会更新“存在的数据”
- 不存在的数据 根本不知道要更新什么
例子
GET user:99999999 (DB 根本没有)
即使你每 5 分钟全量刷新缓存:
- 这个 key 依然不存在
- 请求仍然每次打到 DB
- 缓存空值
- Bloom Filter(布隆过滤器)
- 参数校验
缓存穿透是“访问不存在数据”的问题,定期更新缓存无法感知不存在的数据,因此无效。
回顾定义:
热点 key 失效瞬间,大量并发请求打 DB
定期任务能做什么?
- 在 key 过期前刷新缓存
- 理论上让热点 key 不过期
✅ 在理想情况下有效
1️⃣ 定时任务可能失败
- 任务执行失败
- 数据库抖动
- 网络问题
👉 一旦刷新失败,热点 key 仍然会过期。
2️⃣ 定时任务不是实时的
- 刷新周期是 1 分钟 / 5 分钟
- 在周期空档仍可能过期
3️⃣ 多实例并发刷新
- 多个节点同时刷新
- DB 压力集中
定期任务 + 互斥锁 / 逻辑过期
- 定期任务:减少击穿概率
- 锁 / 逻辑过期:兜底防并发
定期更新缓存只能降低热点 key 失效概率,不能替代互斥锁或逻辑过期机制。
1️⃣ 大量 key 同时过期
2️⃣ Redis 整体不可用
✅ 能缓解:大量 key 同时过期
- 定时任务错峰刷新
- 减少同一时间失效的 key 数量
❌ 完全无能为力:Redis 宕机
- Redis crash
- 主从切换
- 网络隔离
此时:
所有请求 -> DB
不管你有没有定时任务。
- TTL 随机化
- Redis 主从 / Sentinel / Cluster
- 本地缓存
- 限流 & 熔断
定期更新缓存只能缓解“过期型雪崩”,无法应对 Redis 不可用导致的雪崩。
通过计划任务定期更新缓存,只能作为辅助方案,不能解决 Redis 缓存三大问题。
- 对缓存穿透无效,因为数据本身不存在;
- 对缓存击穿只能降低热点 key 失效概率,仍需互斥锁或逻辑过期兜底;
- 对缓存雪崩只能缓解大量 key 同时过期,无法应对 Redis 不可用。
因此生产环境必须结合空值缓存、Bloom Filter(布隆过滤器)、锁机制和高可用架构一起使用。
定期任务(降概率)
+ 逻辑过期(防击穿)
+ 空值缓存 / Bloom(防穿透)
+ TTL 随机(防雪崩)
+ Redis 高可用(兜底)
参考文档:
布隆过滤器(Bloom Filter)是1970年被布隆提出来的,用于判断某个元素是否在一个集合中,优点是空间效率和查询时间都非常高,缺点是有一定的误判率和删除困难。
布隆过滤器(Bloom Filter)是一种空间效率极高的概率型数据结构,由一个超长的二进制位数组和多个哈希函数组成。
它主要用于快速判断元素是否存在于海量数据集合中,特点是能保证“绝对不存在”,但判断“存在”时存在一定误判率(假阳性)。
- 初始化:创建一个全为 0 的超长二进制数组。
- 添加元素:使用多个独立的哈希函数对元素进行计算,将得到的多个位置的比特位由 0 置为 1。
- 查询元素:再次使用相同的哈希函数计算出位置,若对应位置全为 1,则认为元素可能存在;若有任何一位为 0,则该元素一定不存在。
优点:
- 空间效率高:不存储具体元素内容,仅存储比特位,节省内存。
- 查询速度快:时间复杂度为 (O(k)),其中 (k) 为哈希函数个数。
缺点:
- 存在误判(假阳性):可能会将不存在的元素误判为存在(因为哈希碰撞),但不会出现假阴性(判断不存在则一定不存在)。
- 难以删除:通常不支持直接删除元素,因为多个元素可能共享相同的位。
- 防止缓存穿透:在查询数据库前先通过布隆过滤器过滤掉非法请求。
- 海量数据去重:如爬虫过滤已爬取的 URL、垃圾邮件过滤。
- 大数据量判断:检查元素是否存在于庞大的集合中,如 Hbase 和 BigTable 中的行键查询加速。