redis缓存雪崩主因是大量key同一秒集中过期,导致请求穿透压垮数据库;固定ttl(如ex 3600)会加剧该问题,应采用“基础ttl+偏移区间”策略实现随机过期,避免全量随机或负值,并通过redistemplate或自定义cachewriter落地。

生产环境出现 Redis 缓存雪崩,根本原因不是 Redis 崩了,而是大量 key 在同一秒内集中过期,请求穿透到数据库,瞬间压垮后端。固定写死 EX 参数(比如统一设为 3600)等于主动制造雪崩条件。
为什么固定 TTL 会引发雪崩
Redis 过期机制依赖“惰性删除 + 定期采样”,它不会在精确时间点批量清理 key;但一旦这些 key 被访问或被采样到,就会触发删除逻辑。如果成千上万个 user:1001、product:2002 都设了相同的 EX 3600,它们会在写入后第 3600 秒前后密集失效——尤其在定时任务刷新、大促预热、批量导入等场景下,极易形成“整点级失效风暴”。
- 服务器时钟漂移会让
EXPIREAT时间戳实际落在同一秒内 - 批量脚本未加扰动,直接调用
setex(key, 3600, val),所有 key 的生命周期完全对齐 - Spring Cache 的
entryTtl是全局静态值,无法 per-key 控制 - 没区分 key 类型:把
session和dict:status一视同仁设 TTL,关键配置也被误删
怎么设置真正有效的随机 TTL
随机不是越乱越好,核心是让失效压力在时间轴上摊薄,同时保障业务可接受的最短缓存寿命。推荐用“基础 TTL + 相对偏移区间”,而非全量随机。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 对普通缓存 key(如用户资料、商品信息):偏移取基础 TTL 的
10%~15%,例如base_ttl = 3600,则jitter = 360,最终ttl = 3600 + ThreadLocalRandom.current().nextInt(-360, 361) - 对热点 key(如首页 banner、登录态 token):偏移压到
1%~3%,或跳过随机,改用后台定时刷新 +PERSIST延续寿命 - 绝对不要用
Math.random() * 3600或random.nextInt(3600)——这会让部分 key 实际存活不足 60 秒,失去缓存意义 - Java 中必须复用
ThreadLocalRandom,禁用方法内new Random();Python 推荐复用模块级random实例或用secrets.randbelow()(如需密码学安全) - 每次计算完
ttl后必须校验是否 > 0,避免负数导致 key 立即过期
Spring Boot 里绕过 @Cacheable 实现随机 TTL
@Cacheable 注解本身不支持 per-key 动态 TTL,硬编码 entryTtl 或在注解里写 unless="#result == null" 都无效。真要落地,只有两条路:
- 弃用注解,改用
redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(ttl)),其中ttl由你封装的工具方法生成 - 自定义
RedisCacheWriter,继承DefaultRedisCacheWriter,重写put方法,在调用父类前对传入的Duration做扰动;再通过RedisCacheConfiguration.cacheWriter()注入 - 若需按
cacheName区分策略(如users随机、configs固定),得在 writer 内部加路由逻辑,别指望全局配置兜底 - 注意:AOP 拦截
@Cacheable并注入随机逻辑虽可行,但不处理this.调用,且切面顺序必须早于 Spring Cache 默认拦截器
容易被忽略的耦合问题
只加随机 TTL 不等于高枕无忧。线上真正出事的雪崩,往往卡在这些细节:
- 扩容期间 reshark 打破 slot 分布,原分散在不同节点的 key 被迁到同一节点,又因相同 TTL 集中失效
- 没配
maxmemory-policy volatile-lru,误用allkeys-lru导致长期配置 key(如feature:flag)被误删 - 开了
lazyfree-lazy-expire yes就以为安全——它只异步删 key,不阻止请求穿透 - 没监控
expired_keys和evicted_keys的速率突增,错过雪崩前兆(命中率从 95% 跌到 40%) - 把 Caffeine 当 Redis 替代用,却忽略了它不共享、不一致的特性,本地缓存击穿照样打穿 DB










