一句话总览:三个“缓存杀手”都是因为Redis没拦住请求,导致压力转移到数据库,但触发原因和影响范围完全不同。
一、三者对比(一图秒懂)
| 术语 |
一句话定义 |
触发条件 |
影响范围 |
危险等级 |
| 缓存击穿 |
一个热点Key过期,海量请求直击DB |
热点Key在高并发瞬间过期 |
只影响这一个Key对应的业务 |
⭐⭐⭐⭐⭐ |
| 缓存雪崩 |
大批量Key同时过期,DB瞬间爆了 |
大量Key设置了相同的过期时间 |
影响大面积业务,可能导致整体服务不可用 |
⭐⭐⭐⭐⭐ |
| 缓存穿透 |
查询的Key在DB中根本不存在,每次都绕开缓存 |
恶意请求查询不存在的ID(如-1) |
持续打DB,直到DB扛不住 |
⭐⭐⭐⭐ |
二、缓存击穿(Cache Breakdown)
是什么?
一个超级热点数据(比如双11爆款商品、春运车票)在缓存中过期的瞬间,成千上万的并发请求同时发现缓存为空,全部穿透到数据库。
发生原因
- 热点数据设置了过期时间
- 在高并发窗口期内恰好过期
- 大量请求在同一毫秒发现缓存为空,同时查询DB
解决方案
| 方案 |
原理 |
适用场景 |
代码思路 |
| 互斥锁(SET NX) |
只有1个请求能抢到锁去查DB,其他请求等待/重试 |
对实时一致性要求高 |
SET lock 1 NX EX 10 |
| 逻辑过期(永不过期) |
Redis不设物理过期,值里存逻辑过期时间,过期时先返回旧数据,异步更新 |
高并发读多写少(推荐) |
结合BullMQ后台刷新 |
| 热点数据预热 |
流量低谷期(如凌晨)提前刷新热点Key |
可预测的热点(如大促) |
定时任务主动刷新 |
代码示例(逻辑过期 + BullMQ 方案——你现在的技能组合!)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| await SET('hot_goods:1001', JSON.stringify({ stock: 10, price: 99.9, logicExpire: Date.now() + 300000 }));
async function getHotGoods(goodsId) { const cache = await GET(`hot_goods:${goodsId}`); const data = JSON.parse(cache);
if (data.logicExpire < Date.now()) { const lock = await SET(`lock:${goodsId}`, 1, 'NX', 'EX', 10); if (lock) { await refreshQueue.add('refresh-goods', { goodsId }); } return data; } return data; }
refreshQueue.process(async (job) => { const { goodsId } = job.data; const newData = await queryDB(goodsId); await SET(`hot_goods:${goodsId}`, JSON.stringify(newData)); });
|
三、缓存雪崩(Cache Avalanche)
是什么?
大量缓存Key在同一时间集中过期,或者Redis服务本身宕机,导致海量请求直接压垮数据库。
发生原因
- 设计失误:所有Key设置了相同的过期时间(如统一凌晨0点过期)
- Redis宕机:Redis服务挂了,所有请求直接走DB
- 服务器重启:缓存被批量清空后瞬间重建
解决方案
| 方案 |
说明 |
| 过期时间加随机值 |
给过期时间加一个随机偏移量(如 300秒 + random(0,100)),避免集体死亡 |
| Redis高可用 |
使用主从+哨兵或Redis Cluster,保证Redis不宕机 |
| 多级缓存 |
本地缓存(Caffeine/Guava)+ Redis 双层兜底 |
| 限流降级 |
使用Sentinel/Hystrix等组件,当DB压力过大时直接拒绝部分请求 |
代码示例(过期时间加随机值)
1 2 3 4 5 6 7 8
| await SET('goods:1001', data, 'EX', 3600); await SET('goods:1002', data, 'EX', 3600);
const baseExpire = 3600; const randomOffset = Math.floor(Math.random() * 300); await SET('goods:1001', data, 'EX', baseExpire + randomOffset);
|
四、缓存穿透(Cache Penetration)
是什么?
查询一个在数据库中根本不存在的数据(比如用 user_id = -1 查询),因为缓存里没有,每次都穿透到数据库,DB每次都查不到。
发生原因
- 恶意攻击:黑客故意用大量不存在的ID发起请求
- 业务漏洞:前端/客户端传入了非法参数
解决方案
| 方案 |
说明 |
| 缓存空值(Null Value) |
把 null 也缓存起来(SET key null EX 60),下次请求直接返回空,不打DB |
| 布隆过滤器(Bloom Filter) |
在请求到达Redis前,用布隆过滤器判断Key是否可能存在于DB中,不存在的直接拦截 |
| 参数校验 |
业务层先校验ID格式(如 ID > 0),非法的直接返回 |
五、对比
| 对比维度 |
缓存击穿 |
缓存雪崩 |
缓存穿透 |
| 问题本质 |
热点Key过期瞬间的单点爆破 |
大量Key同时过期的集体自杀 |
查询不存在数据的无效攻击 |
| 影响Key数量 |
1个热点Key |
N个Key |
无限(恶意构造) |
| 核心解决思路 |
只让1个人去查DB,其他人等着或拿旧数据 |
分散过期时间,避免同时死亡 |
把“不存在”也缓存起来,或用过滤器提前拦截 |
| 最佳实践方案 |
逻辑过期 + 异步刷新(BullMQ) |
过期时间 + 随机值 |
布隆过滤器 |
| 简单记忆 |
“热点Key过期瞬间高并发穿透” |
“大量Key同时过期导致DB被打” |
“查不存在的数据绕开缓存直击DB” |