Redis缓存雪崩?我用错了过期时间设置!

发布时间:2026/9/29 21:49:18
Redis缓存雪崩?我用错了过期时间设置! 上周四凌晨我们的订单服务在促销活动开始 5 分钟后彻底瘫痪 —— 不是因为流量过大而是因为 Redis 集群突然拒绝服务。监控显示所有缓存 key 在同一秒内失效数据库瞬间被打满 10 万 QPS。你猜问题出在哪正是我们自认为优化过的过期时间设置。灾难现场批量失效的缓存 Key当时我们在处理一个电商秒杀场景订单服务的商品库存缓存采用统一的 24 小时 TTL。这本是个常规操作但问题出在我们为了整洁给所有 key 加上了相同的过期时间戳// 错误示范批量设置相同过期时间 public void cacheProducts(ListProduct products) { try (Jedis jedis jedisPool.getResource()) { Pipeline pipeline jedis.pipelined(); long expireAt System.currentTimeMillis() 24 * 3600 * 1000; for (Product product : products) { String key product: product.getId(); pipeline.setex(key, 24 * 3600, serialize(product)); // 实际等效于全部在次日同一毫秒失效 } pipeline.sync(); } }当这批 key 在第 24 小时集体到期时Redis 的主动淘汰机制瞬间触发大量内存回收操作导致 CPU 飙升至 100%。更致命的是雪崩效应让所有请求穿透到数据库 —— 一个看似无害的代码整洁癖引发了连锁故障。过期时间的底层真相很多人以为SETEX的过期时间是相对命令执行时间计算的但这里藏着一个魔鬼细节Redis 实际存储的是绝对时间戳Unix timestamp。当我们批量执行SETEX时所有 key 的过期时间戳高度集中Redis 的定期淘汰策略activeExpireCycle会扫描这些 key在同一周期内遇到大量过期 key 时主线程会阻塞处理淘汰逻辑用redis-cli查看 key 的真实过期时间就能验证127. 0.0.1:6379 PTTL product:123 (integer) 86399957 # 剩余毫秒数24小时 - 43毫秒正确的随机化姿势解决方案是给过期时间增加随机扰动。但注意简单的Math.random()可能产生负值或破坏缓存一致性// 正确做法基础过期时间 可控随机偏移 private int getRandomizedTtl(int baseTtlSeconds) { int jitter ThreadLocalRandom.current().nextInt(-600, 600); // ±10分钟波动 return Math.max(60, baseTtlSeconds jitter); // 兜底最小1分钟 } // 在原有代码中替换为 pipeline.setex(key, getRandomizedTtl(24 * 3600), serialize(product));实测显示加入 10 分钟随机扰动后Redis 的 CPU 使用率峰值下降 82%数据库 QPS 波动从 10 万降至 3000 左右。那些年我们踩过的 TTL 坑批量导入陷阱使用RESTORE或MSET时默认不会继承过期时间。必须显式指定PTTL参数否则会创建永久 key。时钟漂移灾难在容器化环境中如果宿主机时钟发生跳变Redis 的过期判断可能出现严重偏差。曾遇到过 NTP 同步导致 key 提前 2 小时失效。持久化间隙AOF 重写或 RDB 保存时过期时间可能因持久化延迟而实际延长。对于严格时效性场景需要配合EXPIREAT使用。内存驱逐误判当 Redis 作为 LRU 缓存使用时volatile-ttl策略可能误杀还有很长 TTL 的 key。监控evicted_keys指标至关重要。更聪明的过期策略对于核心缓存我们可以结合多级过期机制// 阶梯式过期 后台刷新 public void cacheWithRefresh(String key, Object value) { int initialTtl getRandomizedTtl(3600); // 第一层1小时 int extendedTtl getRandomizedTtl(24 * 3600); // 第二层24小时 try (Jedis jedis jedisPool.getResource()) { jedis.setex(key, initialTtl, serialize(value)); } // 异步线程在过期前刷新 refreshExecutor.submit(() - { sleep(initialTtl - 300); // 提前5分钟刷新 jedis.expire(key, extendedTtl); }); }这种模式尤其适合需要保证可用性又允许一定脏读的场景 —— 缓存永远不会集体失效且后台刷新能保证数据最终一致性。现在回头看我当初那个整齐划一的 TTL 设置简直像在缓存系统里埋了颗定时炸弹。记住在分布式系统里有时候不整齐才是更高级的秩序。你团队里有没有类似的过度优化反而引发故障的案例欢迎分享你的血泪史。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询