缓存层出问题后,为什么应用和数据库会一起变慢的等待型链路?一次 Redis 客户端超时引发的连锁故障
缓存层故障最怕的不是少了一层加速,而是请求持有时间被一层层拉长。这次事故里,先变坏的是 Redis 客户端超时,随后才是数据库回源、连接池等待和线程排队,一条完整的等待链就是这样被拖出来的。
缓存层出问题以后,很多人的第一反应还是那句老话:大不了退化直连数据库。听起来像是少一层加速器,系统只是慢一点。
可真实事故里,最麻烦的从来不是“多查一次数据库”,而是请求会在每一层都多停一会儿。一旦持有时间被拉长,连接、线程、重试就会跟着一层层堆起来。
上次支付确认链路出事,就是很典型的一次。最开始看到的是应用 RT 抖、数据库连接池变紧、只读库查询上涨,现场看上去像三层一起坏了。可把时间线重新拉直后会发现,第一张倒下的牌其实是 Redis 客户端超时。
第一波异常不是 miss,而是 Redis RT 先抬头
那天 19:42 开始,支付确认接口 confirmPay 的 p99 从 240ms 抬到 1.6s。很多人先去看数据库,因为只读库 QPS 和连接池 active 都在涨。
但把时间线往前拽 2 分钟,最早变坏的是这几条:
- Redis 客户端 p95 从 8ms 抬到 160ms
- 客户端超时数开始增加
- 业务日志里
get cache timeout, fallback to db连续出现
注意,这时候命中率还没有明显掉。也就是说,问题一开始不是“大量 miss”,而是请求先卡在查缓存这一步。
为什么应用和数据库会一起慢下来
因为一次请求的持有时间被拆成了两段等待。
正常时,支付确认链路会先读 Redis 里的用户风控快照和支付路由配置,几毫秒就结束;现在 Redis 先卡 100 多毫秒,失败后还要继续去数据库回源。
这会产生一个很糟糕的连锁:
- 线程先在 Redis 客户端上等一段
- 超时后再去申请数据库连接
- 数据库查询本身变多,连接占用又变长
- 后面的请求因为拿不到连接和线程,再继续排队
于是现场看起来就像数据库和应用一起变慢了,可它们其实是在接同一段被拉长的等待。
这次事故真正的证据链是什么
后来把监控按分钟摊开,顺序非常清楚:
- 19:42 Redis 客户端 RT 抬头,超时数上升
- 19:43
fallback to db日志增多,只读库查询量开始上升 - 19:44 Hikari 活跃连接接近上限,获取连接耗时变长
- 19:45 业务线程池 queue 开始堆积,接口 p99 再次抬高
- 19:46 网关出现重试,请求总量被进一步放大
你会发现,真正一层层把系统拖慢的,不是某个组件突然坏死,而是等待在传导。
根因不是数据库先顶不住,而是 Redis 客户端连接池被打穿
继续查 Redis 侧,服务器本身没有明显故障,节点 RT 也还行。真正出问题的是应用侧 Redis 客户端连接池配置太紧,叠加那一波流量峰值后,连接借用等待开始抬高。
换句话说,最早的缓存故障甚至不在 Redis 服务端,而在客户端拿连接这一步。
这也是这类事故最容易误判的地方:大家看到数据库查询变多、线程池排队,就很自然地往下游找;可如果上游缓存访问本身已经变成慢动作,后面的每一层都会跟着变形。
为什么这次止血先压的是等待,而不是先追根因
当时最先做的几个动作都很“土”,但很有效:
- 临时把 Redis 客户端超时从 200ms 降到 80ms,避免线程长时间吊死在缓存上
- 关掉支付确认链路上的一次自动重试
- 对风控快照加本地 30 秒兜底,减少回源数据库的比例
动作下去以后,回落顺序也很典型:
- 线程池 queue 先停住不再继续涨
- 数据库连接池 active 开始回落
- 只读库 QPS 和接口 p99 才慢慢降下来
这一步特别说明问题:救场时最该先打断的,不是抽象地“恢复缓存”,而是把整条等待链里的持有时间压短。
为什么“缓存坏了就直连数据库”是句危险的话
因为这句话默认了一个过于理想的前提:数据库和应用还有足够余量,足够把多出来的等待吞掉。
可真实线上里,只要你本来就在高峰,或者确认链路本来就重,缓存访问哪怕只多卡几十毫秒,请求持有时间都会明显变长。后面的数据库连接池、业务线程池、网关重试,就会像多米诺骨牌一样一张张倒下。
所以缓存层故障真正危险的地方,不是失去加速,而是它把整条链路都拖进了“资源释放变慢”的状态。
这类现场到底先看什么
如果你看到应用和数据库一起慢,而缓存又有异常,不妨先把下面几件事放到同一条时间线上:
- Redis 客户端 RT 和超时
fallback to db或 miss 后回源日志- 数据库连接池 active / pending
- 业务线程池 queue
- 网关或 RPC 重试量
只要顺序能对上,你通常就能分清:到底是数据库自己先慢,还是缓存把等待一路传下去了。
什么时候这篇思路最有用
当你现场长成下面这样时,这条等待链最值得先看:
- Redis 指标或客户端日志先有抖动
- 数据库和应用几分钟后一起恶化
- 命中率不一定先崩,但回源和排队都在变多
- 超时、重试、连接池等待会一层层接上来
这种形状和单纯 TTL 失效、热 key 打穿、key 设计退化都不太一样。它更像缓存访问先慢了,然后整条业务链都被拖长。
回到这次事故最该记住的一句
缓存层出问题后,应用和数据库会一起变慢,很多时候不是因为它们各自都坏了,而是同一批请求在每一层都多等了一会儿。
所以排查这种故障时,我现在最先看的已经不是“哪个组件更红”,而是:谁先开始拉长请求持有时间。 一旦这一步看清,止血动作也会更直接——先把等待截断,再谈更细的根因修复。