缓存雪崩是大概率在流量高峰爆发的问题,因@Cacheable默认不支持per-key随机TTL,统一固定过期时间导致key集中失效;必须通过RedisTemplate手动写入并添加随机偏移(如300±30秒)来缓解,同时需结合本地缓存、降级开关与熔断器构建多层防护。

缓存雪崩不是“会不会发生”的问题,而是“什么时候爆发”的问题——只要大量 key 设置了相同 ttl,或 Redis 实例单点宕机,雪崩就大概率在流量高峰时触发。
为什么 @Cacheable 默认配置会放大雪崩风险
Spring Boot 的 @Cacheable 注解默认不控制过期时间分散性,且 CacheManager(如 RedisCacheManager)对所有缓存项统一应用固定 ttl。一旦业务批量预热商品、用户、配置等数据并设置统一过期策略(比如都设为 300 秒),这些 key 就会在同一窗口集中失效。
- 现象:凌晨 2:00 大促开始前 5 分钟,缓存批量过期,数据库 QPS 瞬间从 3k 拉到 40k,连接池打满
- 根本原因:
@Cacheable(value = "products", key = "#id")不带随机化逻辑,RedisCacheConfiguration中的entryTtl是全局静态值 - 兼容性影响:Spring Boot 2.7+ 的
RedisCacheConfiguration支持 per-cache 配置,但依然不支持 per-key 动态 ttl
必须手动干预:给每个 key 注入随机过期时间
不能依赖注解自动完成,必须在缓存写入阶段显式控制 ttl。推荐在自定义 CacheManager 或服务层封装中实现:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用
RedisTemplate替代纯@Cacheable:绕过 Spring Cache 抽象,直接调用opsForValue().set(key, value, duration, timeUnit) - 生成随机
ttl的安全范围建议:基础值 ±10%~30%,例如基础 300 秒,则取300 + random.nextInt(90) - 45(即 255~345 秒) - 避免使用
System.currentTimeMillis()做偏移——它会导致不同节点时间差引发不一致;应统一用ThreadLocalRandom.current().nextInt()或注入的Randombean - 示例关键代码:
String key = "product:" + productId; String json = JSON.toJSONString(product); long baseTtl = 300; long jitter = ThreadLocalRandom.current().nextLong(-30, 31); // ±30秒 redisTemplate.opsForValue().set(key, json, baseTtl + jitter, TimeUnit.SECONDS);
Redis 单点故障场景下必须有兜底链路
即使做了过期时间分散,Redis 宕机仍会导致全量缓存不可用——此时 @Cacheable 会直接 fallback 到方法执行,所有请求击穿到 DB。
- 不要只依赖 Redis:必须引入本地缓存(如
Caffeine)作为第一道屏障,哪怕只缓存 1000 个热点key,也能挡住 60%+ 请求 - 本地缓存需独立过期策略:例如
Caffeine.newBuilder().expireAfterWrite(2, TimeUnit.MINUTES),与 Redis 的 5 分钟错开,避免共振失效 - 降级开关要可动态生效:通过
@ConditionalOnProperty或配置中心(如 Nacos)控制是否启用 Redis,故障时一键切到本地缓存 + 限流 - 熔断器不是可选件:对数据库查询方法加
@CircuitBreaker(fallbackMethod = "getFallbackProduct"),防止 DB 崩溃后线程池耗尽
最易被忽略的一点:过期时间随机化只解决“集中过期”,不解决“缓存层整体不可用”。生产环境必须把 Redis 故障当成常态来设计——本地缓存、降级接口、DB 限流、监控告警这四层,缺一不可。

















