写下这篇的时候网站刚恢复一小时。这是一篇给自己看的复盘,也给看到这篇文章的你提个醒。
事故经过
21:15,运营群炸了:首页商品列表打不开。监控上看,接口大量超时,数据库连接池打满,QPS 是平时三倍——都是打到 MySQL 上的。应用日志里全是 Redis 超时异常,但 Redis 本身内存、CPU 都正常。
21:30 定位到原因:商品分类的缓存 key 全部设置了固定的 24 小时过期,而这批 key 是某天 21:14 一次性写入的(当时做过一次全量刷新)。于是每天 21:14,几百个 key 集中过期,请求全部穿透到数据库,慢查询堆积 → 连接池耗尽 → 全站雪崩。之前为什么没炸?之前流量低,数据库扛得住。今天上了个推广活动,流量翻倍,就炸了。
当时的止血动作
- 临时把活动页降级成静态兜底页(预案里写好的,从没演练过,找开关找了十分钟)
- 手动预热缓存:跑了一遍全量分类数据的加载,key 恢复
- 22:50 左右全量恢复
事后的长效改进
- 过期时间加随机数:TTL = 基础值 + random(0, 600s),把过期时间打散。一行代码的事,为什么不早点做
- 互斥重建:缓存失效时用 setnx 抢锁,只有拿到锁的那一个请求去查库回填,其余请求短暂等待后读旧值或返回兜底数据
- 热点 key 永不过期 + 逻辑过期:value 里带过期时间字段,后台异步刷新,物理 TTL 设很大
- 下游保护:数据库连接池 + 接口侧限流,保证穿透时也不至于全站瘫
- 演练:降级开关每季度真的按一次,确认它还能用
这次事故最大的教训不是技术上的——雪崩的每个解决方案网上一搜一大把——而是"不会出错的东西"恰恰是最大的隐患。缓存平时太可靠了,可靠到没人想过它集体失效时系统是什么样。以后做任何设计,先问一句:它挂了会怎样?

Comments 4 条评论
批量写入的 key 同时过期,太典型了
TTL 加随机数一行代码就能救命
降级开关从没演练过,找十分钟太真实
不会出错的东西才是最大隐患,精辟