缓存三大问题(缓存雪崩、缓存穿透、缓存击穿)及防护手段

一、Redis 缓存雪崩、击穿、穿透三大问题

雪崩是大面积 key 过期或节点故障,解决靠随机过期+集群高可用;
击穿是单个热点 key 失效,用互斥锁或逻辑过期;
穿透是查询不存在数据,用布隆过滤器+空值缓存拦截。

1.1、缓存雪崩(Cache Avalanche)

维度要点
定义大量 key 同时过期Redis 整体宕机,导致所有请求直达 DB。
触发场景• 批量 key 设置相同过期时间(如整点 00:00)
• 节点故障、机房断网。
危害DB 瞬时 QPS 暴涨 → 连接池耗尽 → 服务整体不可用。
防御方案过期时间加随机值(打散)
set key value EX $((base+rand))
永不过期 + 异步刷新(对热点 key)
高可用集群(主从+哨兵 / Cluster)
多级缓存(本地 Caffeine + Redis)
熔断降级(Sentinel / Hystrix)

1.2、缓存击穿(Cache Breakdown)

维度要点
定义单个热点 key 在失效瞬间被高并发冲击,所有请求打到 DB。
触发场景• 热点 key 过期或被主动删除
• 微博热搜、秒杀商品。
危害相比雪崩影响面小,但并发更高,可能打挂单节点 DB。
防御方案互斥锁(mutex)
java<br>boolean lock = redisTemplate.opsForValue()<br> .setIfAbsent("lock:sku123", "1", 5, TimeUnit.SECONDS);<br>if (lock) { /* 查库并回写缓存 */ }<br>
逻辑过期(value 里存过期时间,异步线程刷新)
热点 key 永不过期 + 后台定时更新

1.3、缓存穿透(Cache Penetration)

维度要点
定义大量请求访问根本不存在的数据 → Redis 无值 → DB 也无值,每次穿透。
触发场景• 恶意攻击随机 ID
• 代码 bug 传入非法参数。
危害DB 空查也占连接,容易被刷爆。
防御方案布隆过滤器(预加载合法 ID)
空对象缓存(short-term)
setex not:sku999 "" 30s
参数合法性校验(网关层 / 业务层)

2、防护手段
实际项目中,我们在秒杀场景把三种手段叠加,最终把 DB QPS 控制在 200 以内。

  • 本地 Caffeine 抗雪崩,
  • Redis 互斥锁防击穿,
  • 布隆过滤器挡穿透,

2.1、本地 Caffeine 抗雪崩(L1 缓存)

维度说明
问题定位当 Redis 大面积失效或网络隔离,流量直接打到 DB,形成 缓存雪崩
工作原理每台应用实例 内再建一级 进程内缓存(Caffeine)。
• 读:先查 Caffeine → 未命中再查 Redis → 仍未命中才查 DB。
• 写:DB 更新后,先清 Redis,再 异步广播 清理本地缓存(基于 Redis Pub/Sub 或 Canal)。
关键配置java<br>Caffeine.newBuilder()<br> .maximumSize(10_000) // 最大条目<br> .expireAfterWrite(30, TimeUnit.SECONDS)<br> .recordStats() // 监控命中率<br> .build();<br>
优势1. 无网络 RTT,单机 QPS 可提升 1~2 个量级。
2. Redis 故障时仍能提供 有损服务,避免雪崩。
注意点• 内存占用:10 万条对象 ≈ 几十 MB。
• 一致性:本地 TTL 设短,或主动失效。

2.2、Redis 互斥锁 防击穿(L2 缓存)

维度说明
问题定位单个热点 key 失效瞬间,大量并发击穿到 DB。
工作原理只有一个线程能拿到锁,其余线程 阻塞或重试;拿到锁的线程去 DB 查询并回写缓存。
最简代码(Lua 原子锁)java<br>// 1. 抢锁<br>Boolean locked = redisTemplate.execute(<br> RedisScript.of(<br> "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +<br> " redis.call('expire', KEYS[1], ARGV[2]); return 1; " +<br> "else return 0; end"),<br> Collections.singletonList("lock:sku123"),<br> UUID.randomUUID().toString(), "5");<br>// 2. 释放锁:用 Lua 保证“谁加的谁释放”<br>
Redisson 高级方案RLock lock = redisson.getLock("lock:sku123");
• 自带 看门狗(每 1/3 TTL 自动续期)防止业务执行时间 > TTL。
注意点• 锁超时必须 大于 99% 业务耗时
• 高并发下 阻塞线程数 不能太多,可改用 逻辑过期(见下)。

2.3、布隆过滤器 挡穿透(L0 前置)

维度说明
问题定位请求的是 根本不存在的数据(如 id = -1),Redis & DB 都无结果,导致每次穿透到 DB。
工作原理• 把所有 合法 key 预先通过 k 个 hash 函数映射到一个 位数组(bit array)。
• 查询时先走 BloomFilter:
 – 若返回 0 → 一定不存在,直接返回空。
 – 若返回 1可能存在(有极低误判率),继续查 Redis。
代码示例(Guava)java<br>BloomFilter<String> bf = BloomFilter.create(<br> Funnels.stringFunnel(Charset.defaultCharset()),<br> 1_000_000, 0.01); // 100w 条,误判率 1%<br>// 初始化<br>productIds.forEach(id -> bf.put(id));<br>// 使用<br>if (!bf.mightContain(requestId)) {<br> return Result.empty();<br>}<br>
Redis 模块• Redis 4.0+ 原生 RedisBloom 模块:BF.ADD / BF.EXISTS
• 误判率可调:BF.RESERVE bf 0.001 500000
注意点• 只能 增查,不能 (计数 BloomFilter 除外)。
• 误判率设太低 → 位数组过大,占用内存。

面试 30 秒话术:
我们线上用三级缓存三高:
• 布隆过滤器 在网关层先挡穿透;
• Caffeine 做 L1,30 s TTL,Redis 故障时兜底;
• Redis 互斥锁(Redisson)防击穿,看门狗续期,锁超时 5 s;
上线半年,缓存命中率 96%,DB QPS 从 3 w 降到 1 k。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值