4. 缓存雪崩:大规模缓存同时失效,多级缓存架构与降级策略
缓存雪崩,说白了就是一大片缓存同时挂了。
不是单个key失效,是成百上千个key,在同一时刻集体罢工。这时候所有请求直接穿透到数据库,你想想看,数据库扛得住吗?
我在项目中遇到过这种场景。有一次大促活动,零点刚过,系统瞬间被打爆。排查下来,不是代码bug,是缓存同时过期了。嗯,那感觉,真是刻骨铭心。
4.1 缓存雪崩的本质
缓存雪崩和缓存穿透不一样。穿透是单个key没命中,雪崩是大面积key同时失效。
为什么会这样?
最常见的原因就两个:
- 缓存同时过期:比如你给一批商品设置了相同的过期时间,比如凌晨0点过期。那到了0点,这批缓存全部失效,请求全打到数据库。
- 缓存节点宕机:比如Redis集群某个分片挂了,那这个分片上的所有缓存都不可用。如果流量大,其他节点也可能被拖垮。
核心问题:缓存雪崩的本质是「保护层失效」,导致底层系统承受远超预期的压力。
4.2 多级缓存架构
我个人习惯用多级缓存来对抗雪崩。说白了,就是给系统多穿几层防弹衣。
典型的架构是这样的:
客户端 → CDN缓存 → 本地缓存(Caffeine) → 分布式缓存(Redis) → 数据库
每一层都承担一部分流量。上层扛不住,还有下层兜底。
我画了一张图,你看一下这个流程:
4.3 缓存雪崩的预防策略
预防永远比事后补救划算。我总结了几个实战中验证过的策略:
4.3.1 过期时间打散
别把所有key的过期时间设成一样的。加个随机偏移量。
// 错误做法:所有key同时过期
redis.set(key, value, 3600);
// 正确做法:基础时间 + 随机偏移
int baseExpire = 3600;
int randomOffset = new Random().nextInt(600); // 0-600秒随机
redis.set(key, value, baseExpire + randomOffset);
我曾经在一个项目中,把所有商品缓存都设成了24小时过期。结果每天凌晨3点,数据库准时被打爆。后来加了随机偏移,问题就解决了。
4.3.2 缓存预热 + 互斥锁
对于热点数据,提前加载到缓存。同时用互斥锁防止并发重建缓存。
// 缓存重建时加锁
String lockKey = "lock:" + key;
boolean locked = redis.setNx(lockKey, "1", 5); // 5秒超时
if (locked) {
try {
// 从数据库加载数据
Object data = db.query(key);
redis.set(key, data, expireTime);
} finally {
redis.del(lockKey);
}
} else {
// 没拿到锁,等待后重试
Thread.sleep(50);
return redis.get(key); // 可能拿到旧数据
}
小技巧:互斥锁的粒度要细。别锁整个缓存,只锁当前key。否则性能会下降得很厉害。
4.3.3 二级缓存永不过期
对于核心数据,比如用户信息、商品详情,我建议用二级缓存。一级缓存有过期时间,二级缓存永不过期。
一级缓存失效了,从二级缓存拿数据。同时异步去数据库刷新一级缓存。
// 一级缓存:短过期
Object data = redis.get(key);
if (data == null) {
// 从二级缓存拿
data = localCache.get(key);
if (data != null) {
// 异步刷新一级缓存
executor.submit(() -> {
Object freshData = db.query(key);
redis.set(key, freshData, 300);
});
}
}
4.4 降级策略
缓存雪崩发生了怎么办?别慌,降级。
降级不是不处理,是「有损服务」。保证核心功能可用,非核心功能暂时关闭。
| 降级级别 | 触发条件 | 处理方式 | 影响范围 |
|---|---|---|---|
| 一级降级 | 缓存命中率 < 50% | 返回默认值/静态数据 | 部分数据不实时 |
| 二级降级 | 数据库QPS > 阈值 | 限流 + 排队 | 部分请求延迟 |
| 三级降级 | 数据库连接池耗尽 | 直接返回失败提示 | 部分请求失败 |
注意:降级一定要有「熔断恢复」机制。别降级了就回不来了。我见过一个系统,降级后忘了恢复,结果跑了三天「降级模式」,用户投诉一大堆。
4.5 实战避坑指南
最后分享几个我踩过的坑:
- 别依赖单一缓存:Redis再稳也有宕机的时候。本地缓存 + Redis 双保险。
- 监控缓存命中率:命中率突然下降,就是雪崩的前兆。我习惯在监控面板上放这个指标。
- 数据库连接池要限流:缓存雪崩时,数据库是最后一道防线。连接池设个上限,别让数据库被打死。
- 降级开关要手动+自动:自动降级可能误判,手动降级又太慢。两者结合最稳妥。
我曾经在一个金融交易系统里,就因为没做缓存降级,导致数据库连接池被打满,整个交易停了5分钟。嗯,从那以后,我再也不敢忽视缓存雪崩了。
总结一句话:缓存雪崩不可怕,可怕的是没有预案。多级缓存 + 过期时间打散 + 降级策略,这三板斧用好了,系统稳如老狗。
公众号:蓝海资料掘金营,微信deep3321