缓存雪崩导致服务不可用是因为大量 key 同时过期,缓存集体失效,请求穿透至数据库致其过载宕机;此时后端服务因连接池耗尽、线程阻塞等被压垮,用户看到 502/504 或白屏。

缓存雪崩发生时,服务为什么突然不可用
因为大量 key 同一时间过期,Redis 中缓存集体失效,所有请求穿透到数据库,DB 瞬间被打满,响应超时甚至宕机。这时候不是 Redis 挂了,而是后端服务被压垮——连接池耗尽、线程阻塞、HTTP 超时堆积,用户看到的是 502/504 或白屏。
加随机过期时间不能只靠 EXPIRE 手动设
单纯在写入时用 EXPIRE key 3600 设固定 TTL,上线后批量导入或定时刷新容易导致过期时间对齐。必须让过期时间天然“打散”:
- 写入时统一用
SETEX key ttl_with_random value,其中ttl_with_random = base_ttl + random(0, 300)(比如基础 1 小时,再加 0–5 分钟随机偏移) - 如果用 Spring Data Redis,别直接调
opsForValue().set(key, value, Duration.ofSeconds(ttl)),而要先算好带偏移的Duration - 注意:
Redisson的RBucket.set()默认不带随机,得自己封装一层
降级开关要能秒级生效,不能依赖配置中心轮询
雪崩往往发生在凌晨或大促峰值,等 Nacos/Apollo 拉取新配置可能已晚。降级逻辑必须本地可兜底:
- 用
AtomicBoolean或ConcurrentHashMap存本地开关,HTTP 接口如/actuator/feature-toggle?cache=off直接触发set(true) - 降级时绕过 Redis,但不要直接查 DB——查 DB 仍是瓶颈。应优先返回托底数据:
Map静态缓存、JSON 文件预加载、或上一次成功缓存的快照(需定期SAVE到本地磁盘) - 避免降级后全量走托底还把磁盘 IO 打满:托底数据读取加
FileChannel.map()内存映射,或用Guava Cache做二级缓存
try-catch 捕获 Redis 异常不够,要区分失败类型
不是所有 Redis 报错都该降级。比如 JedisConnectionException 是连接断了,该熔断;但 JedisDataException: NOAUTH 是配置错了,不该降级,而该告警+人工介入:
- 在 DAO 层按异常类型分流:
if (e instanceof JedisConnectionException || e.getCause() instanceof SocketTimeoutException)→ 触发降级 - 对
JedisDataException,只记录 ERROR 日志并上报监控(如 Prometheus + AlertManager),不走托底逻辑 - 注意:Lettuce 的异常体系不同,
RedisCommandExecutionException对应命令错误,RedisConnectionClosedException才是连接问题,别混用判断逻辑
真正难的不是写降级代码,而是托底数据的时效性与一致性——静态 JSON 文件更新滞后、快照过期、内存映射未及时 reload。这些细节没对齐,降级就变成“有数据,但全是错的”。











