RPC 超时但下游不慢,很多时候是请求根本没稳定到下游
RPC timeout 而下游 RT 看起来正常时,排查最容易被带偏到“下游是不是没暴露出来”。很多真实现场里,请求预算其实消耗在连接池、代理层或回调线程这些中间环节,请求根本没稳定到下游。
RPC timeout 最让人别扭的一种现场,是调用方已经开始成片超时了,下游却说自己没慢。
这种时候,排障讨论特别容易卡住。
调用方会觉得:
- 都 timeout 了,下游怎么可能没问题?
下游会觉得:
- 我收到的请求没那么多,RT 也没炸,为什么锅先到我这?
两边都不算胡说,但如果只围着“下游到底慢没慢”打转,现场很容易越查越僵。
因为这类问题里,一个很常见、也很容易被漏掉的事实是:
请求未必真的稳定到了下游。
也就是说,timeout 不一定发生在下游业务执行阶段,它也可能发生在请求到达下游之前,或者响应回来以后、但还没来得及被调用方正确处理的时候。
我更愿意把这类问题压成一条很短的判断线:
- 不是先问下游慢不慢
- 而是先问请求有没有稳定到下游
只要这一步没搞清,现场就会一直在“你说你没慢,我说我都 timeout 了”这种争论里打转。
为什么“下游 RT 正常”并不能说明链路没问题
因为调用方感知到的 RPC 耗时,本来就不只是下游业务处理时间。
一条 RPC 从调用方视角看,前后至少还夹着几段很容易被低估的成本:
- 借连接
- 发请求
- 经过代理或 sidecar 转发
- 等响应回来
- 回调线程分发结果
只要这几段里有一段明显变长,调用方看到的就是 timeout。
而下游常说的“我这边 RT 正常”,很多时候只覆盖了它真正收到请求之后的那一小段处理时间。
所以两边看到的不是同一段时间,本来就可能同时成立:
- 调用方大量 timeout
- 下游 RT 仍然正常
这不是谁在撒谎,而是预算可能在中间层被吃掉了。
这类现场里,我最先怀疑的通常不是所有中间层,而是三段最常见的位置
原文如果把所有中间层一股脑铺开,很容易又写成一篇知识库总览。真到值班现场,我更常先盯下面三段。
第一段:调用端连接池
这是特别高频、也特别容易被忽略的一层。
如果请求在真正发出去之前,要先拿连接、等可用连接、或者频繁建连,那么 timeout 预算可能在出门前就已经耗掉一截了。
这时现场常会看到:
- 调用方超时很多
- 下游收到量却对不上
- 问题集中在部分实例
- 高峰时更明显
也就是说,请求不是到了下游以后慢,而是还没稳定发出去。
第二段:代理层或 Sidecar
如果链路里有 Service Mesh、Envoy 或其他代理层,这一段也特别值得优先怀疑。
因为代理层一旦出现局部抖动,会形成一种很迷惑的现象:
- 调用方 timeout 很明显
- 下游业务实例并没有同步恶化
- 问题还可能只集中在部分节点、部分机房
这时候如果你只盯下游应用日志,往往会觉得“什么都没发生”。可实际上请求可能在代理转发、连接复用或局部摘流那里就已经不稳定了。
第三段:回调线程或 EventLoop
还有一种很像“下游不慢但我还是 timeout”的问题,出在响应其实已经回来了,只是调用方没来得及把它处理完。
比如:
- EventLoop 被阻塞
- 回调线程池拥堵
- future / promise 迟迟不能完成
这时从下游看,请求处理完了;从调用方看,却还是等到了 timeout。
所以这类问题最别扭的地方就在这里:不是请求没执行,而是结果没及时回到业务线程眼里。
一个更像现场的判断办法:先对齐“发起量”和“收到量”
如果以后再碰到“RPC timeout,但下游不慢”,我最建议先做的不是翻所有框架细节,而是先对齐一件很朴素的事:
- 调用方到底发起了多少
- 下游到底稳定收到了多少
这一步特别值钱。
如果调用方记录的发起量很高,下游实际收到量却明显少一截,那就别再围着“下游执行慢不慢”争了,重点该立刻转向中间层。
因为这已经很像:请求压根没稳定到下游。
只有当两边量能对上,再去比较下游处理期 RT,才更像在同一条线上讨论。
为什么这条线能帮现场少走弯路
因为它把一个很容易失焦的问题重新拉直了。
很多人一看到“RPC timeout + 下游 RT 正常”,就会下意识进入两种无效动作:
- 不断逼下游再查一遍
- 或者开始列一大堆可能中间层,结果越列越散
但如果先抓住“请求有没有稳定到下游”这条线,方向会收得很快。
如果没稳定到,下一个重点自然就是:
- 连接池
- 代理层
- 回调线程 / EventLoop
这三段已经能覆盖现场里很大一部分真实问题,而且它们都能解释那个最关键的矛盾:
- 下游看起来没慢
- 调用方却真的在 timeout
最后压成一句话
RPC timeout 而下游不慢时,最怕的不是暂时还没定位,而是一开始就默认“请求肯定到了下游,只是下游没承认”。
很多时候,请求预算真正耗掉的地方,在连接池、代理层或回调线程,请求根本没稳定到下游。
先把这件事判断清楚,现场才不会一直在错误的问题上打转。