凌晨三点的缓存雪崩,教会我给系统留后路
凌晨三点零七分,手机在床头柜上震动。那不是普通的提醒,而是持续不断的告警电话。我接起来,值班同事的声音发紧:核心商品接口成功率从九十九点九八掉到六成,Redis 集群 CPU 打满,数据库连接池也快撑爆了。我打开电脑,屏幕上是一片刺眼的红色。那是 2026 年深秋的一个凌晨,窗外没有雪,系统里却真的发生了一场雪崩。
我第一反应是缓存集群出了问题,差点顺手点下重启。可当我看到监控曲线,手停住了:缓存命中率不是缓慢下滑,而是在三分钟里垂直坠落;数据库 QPS 却像被拔掉闸门的水库,瞬间涨了十几倍。这不是单个节点故障,而是一批热点键在同一时间过期,所有请求同时穿过缓存,扑向后端。重启缓存只会让剩余数据一起消失,把雪崩变成雪崩加塌方。那一刻我才明白,真正可怕的不是缓存坏了,而是我们从来没有认真设计过缓存坏掉之后怎么办。
一、凌晨三点的电话
事后复盘,触发点并不复杂。零点左右,预热脚本给一批活动商品写入缓存,TTL 统一设成三小时;凌晨三点,这批键整齐地过期。偏偏同一时间,合作方回调出现重试风暴,流量比平时高出数倍。缓存层没有互斥锁,没有请求合并,本地缓存也没有兜底,所有请求像听到发令枪一样冲向数据库。数据库连接池被瞬间占满,慢查询排队,接口超时,上游重试,雪崩就这样一层层放大。
我们那晚的处置并不漂亮。先关掉非核心推荐模块,再对商品详情做限流,把部分写请求降级为只读,然后人工分批回填缓存,给 TTL 加上随机抖动。系统在四点二十分左右恢复,但我在工位上坐到天亮。最让我难受的不是故障本身,而是我们明明做了缓存,却把缓存当成了永远不会缺席的队友。它一倒下,整个系统连走路都不会了。
二、雪崩不是一瞬间发生的
很多人把缓存雪崩理解成“大量键同时过期”,这只说对了一半。真正的事故从来不是单点问题,而是一串设计假设同时失效:假设缓存永远命中,假设数据库能接住所有流量,假设重试不会形成风暴,假设降级开关随时可用。平时这些假设都被高命中率掩盖着,直到某个凌晨,它们一起到期。
缓存击穿、缓存穿透、缓存雪崩,名字不同,本质都在问同一个问题:当依赖不可用时,核心业务还能不能活。我们过去太爱看命中率,太少看失效路径;太爱算缓存带来的性能收益,太少算缓存消失后的生存时间。一个系统成熟不成熟,不看它巅峰时跑得多快,而看它最坏时退得多稳。雪崩不是一瞬间发生的,它是设计债在凌晨三点集中兑付。
三、后路不是备份
事故之后,我反复琢磨“留后路”这四个字。后路不是多买一台机器,也不是再搭一个备份集群。备份是同一路径上的副本,后路是另一条可走的路。缓存挂了,能不能读本地缓存;数据库压力过大,能不能返回短时旧数据;核心链路超时,能不能先降级推荐和评论;写请求失败,能不能进入队列稍后重试。后路的本质,是让系统在失去某个能力时,仍然能提供有边界的服务。
但后路也不是无限兜底。没有边界的兜底会把风险藏起来,让问题更晚、更大地爆发。真正的后路要有明确边界:哪些功能可以降级,降级后数据旧到什么程度可接受,哪些请求必须拒绝,多久没有恢复就升级告警。它不是让系统假装没事,而是让系统诚实地说:我现在只能做到这一步,但核心承诺还在。
四、我给系统留后路的五条原则
第一,把时间打散。所有缓存键统一过期,等于给系统埋了一颗定时炸弹。TTL 要加随机抖动,预热要分批,热点数据可以采用逻辑过期加异步刷新。不是所有键都必须同一时刻重新加载,错峰本身就是一种容错。
第二,让请求收敛。同一个热点键失效时,不应该让一千个请求同时回源。用单飞机制、互斥锁或请求合并,让一个请求去加载,其他请求等待或读取旧值。锁不是万能的,但在雪崩边缘,它能把洪峰削成细流。
第三,建多层缓存。分布式缓存之外,保留短 TTL 的本地缓存;允许 stale-while-revalidate,让旧数据在后台刷新时继续服务一小段时间。用户看到的也许不是最新价格,但至少不是一片空白。对很多场景来说,旧一点比挂掉好。
第四,把降级写成代码,而不是写成口号。核心与非核心要有清单,熔断、限流、只读、静态兜底都要有开关,并且定期验证。降级开关最怕的不是没有,而是事故来时没人敢按、按了不生效。
第五,用演练和观测守住后路。告警不能只看接口成功率,还要看缓存命中率、回源 QPS、数据库连接池、重试次数和降级开关状态。平时做故障演练,把缓存整体不可用当成必答题。后路只有走过,才算路。
五、留后路是一种工程伦理
这次事故之后,我在设计评审里加了一个问题:如果这个依赖此刻消失,核心路径怎么走?这个问题常常让讨论变慢,但它让系统变得更诚实。我们总以为工程能力体现在把系统做得更强,后来才懂,成熟也体现在承认系统一定会坏。留后路不是悲观,而是对不确定性的敬畏,是对用户时间的尊重。
用户不会在凌晨三点关心我们的架构图,他们只关心能不能下单、能不能支付、页面是不是白屏。系统可以降级,信任不能降级。留后路需要成本,也需要取舍,但这份成本不该等到事故后再付。复盘可以不追责,但不能没有动作;可以接受不完美,但不能接受同一个坑反复出现。
六、结语:把后路修在平时
今天再听到凌晨告警,我依然会紧张,但不再只想着“赶紧恢复”。我会先问:核心链路还在吗,降级开关有效吗,最坏情况被限制住了吗。缓存雪崩教会我的,不只是几个技术手段,而是一种做系统的方式:在追求效率的同时,给失败留位置;在相信依赖的同时,准备依赖缺席。
后路不是给懦弱准备的,它是给不确定性准备的座位。系统一定会坏,区别只在于坏的时候,我们有没有第二条路可走。愿我们都不必等到凌晨三点,才学会把后路修在平时。