网关 504、应用超时、客户端超时,到底谁先超时?
真正把人带偏的,往往不是 504 本身,而是每一层都在用自己的秒表报错。把客户端、网关、应用到下游的超时预算摆成一条时间线,才能看清是谁先耗尽等待,谁只是最后把故障喊出来。
19:47,一条投诉进来:用户说客户端 2 秒就报超时。几乎同时,网关面板上 504 抬头,应用里也开始出现下游 Read timed out。
群里很快开始争:
- 客户端都先超时了,肯定是前端或网络问题。
- 网关 504 最多,先查网关。
- 应用日志里已经有 timeout,根因肯定在应用。
那次真正有用的不是继续争“谁先报错”,而是把同一个 requestId 在三层日志里串起来:
| 时间 | 客户端 | 网关 | 应用 | 下游 |
|---|---|---|---|---|
| 19:47:12.001 | 发起请求 | 收到请求 | 收到请求 | - |
| 19:47:12.034 | - | 转发完成 | 开始查订单、再调库存 | - |
| 19:47:12.841 | - | 仍在等待 | 第一次库存调用 read timeout(800ms) | 库存 RT 抬高 |
| 19:47:12.845 | - | 仍在等待 | 触发重试,再调一次库存 | - |
| 19:47:13.646 | - | 仍在等待 | 第二次库存调用再次超时 | 库存仍慢 |
| 19:47:13.801 | 客户端超时返回 | 网关仍未放弃 | 应用还在做收尾 | - |
| 19:47:13.830 | - | 网关返回 504 | 应用 finally 结束 | - |
这张时间线一出来,很多争论就没必要了。客户端、网关、应用看到的是同一条请求,但它们拿的是不同预算:
- 客户端愿意等 1.8 秒。
- 网关愿意等 1.82 秒到 2 秒。
- 应用内部一个下游调用 800ms,还叠了一次重试。
所以“谁先报错”只是表象;真正该问的是:哪一层先把自己的等待预算花光了。
先画预算,再谈根因
很多超时现场之所以越查越乱,是因为大家默认三层在用同一把秒表。
实际往往不是。
我更愿意先拉出下面这张预算表:
| 层级 | 超时配置 | 这层实际在等谁 |
|---|---|---|
| 客户端 | 1800ms | 等网关完整返回 |
| 网关 | 1900ms | 等应用返回 |
| 应用入口 | 2000ms | 等内部业务处理完成 |
| 应用到库存 | 800ms,重试 1 次 | 等库存服务 |
只要这张表一摆出来,很多现场就能立刻看出危险形态。
比如上面那次:应用内部一个下游调用最多就可能消耗 1600ms 左右,再加上本地处理和重试间隔,已经非常接近客户端和网关预算了。此时只要库存 RT 稍微再抬一点,客户端超时、网关 504、应用 timeout 就会像连锁反应一样一起出现。
所以别再问“504 是不是比应用 timeout 更接近根因”,要问的是:是谁先让整条链没有预算余量了。
网关 504 很响,但常常只是中继站先吹哨
504 之所以容易把人带偏,是因为它最显眼,且看起来像“网关出错”。
可在很多现场里,网关只是第一个站出来说:“我替你等到这里为止了。”
更像“504 只是外层表现”的信号有这些:
- 同一时间应用 access log 里能看到大量长请求,而不是请求根本没到应用。
- 应用内部线程池、下游 RT、连接池等待已经在 504 之前抬头。
- 降低某个下游等待或关掉一次重试后,504 很快跟着掉下去。
相反,如果你看到的是:
- 应用几乎没收到请求。
- 网关自身 upstream connect、DNS、TLS 就已经大量失败。
- 网关到应用之间的网络错误先抬头。
那才更像 504 真要回头查网关或中间网络层。
四种常见时间线,长得很像,处理顺序却不一样
1. 客户端先超时,网关和应用都还算安静
更像:
- 客户端预算太短。
- 移动端 / 前端超时阈值明显小于网关和应用。
- 用户已经先放弃,但服务端还在继续处理。
这时要先核对客户端预算和业务可接受时延,而不是立刻说网关或应用坏了。
2. 网关 504 很多,应用 access log 里也有一批长请求
更像:
- 请求已经进入应用。
- 应用内部某段等待先把预算吃掉。
- 网关只是比应用更早放弃。
这类最值得补的是应用内等待证据:线程池、连接池、下游 RT、锁等待。
3. 应用里先出现统一阈值附近的 timeout,随后网关 504 抬头
更像:
- 下游或应用内部超时先发生。
- 网关 504 是外层被动暴露。
- 如果还叠了重试,网关的报错会越来越多。
这种现场尤其不能只盯网关面板。真正的起点往往已经在应用内部了。
4. 网关 504 和客户端超时都有,但应用几乎没收到请求
更像:
- 网关到应用之间建连有问题。
- 服务发现、DNS、TLS、目标实例状态异常。
- 请求在到应用前就把预算耗掉了。
这时再去翻应用线程池和连接池,意义通常不大。
我在现场最想先拿到的,不是更多报错,而是这三份东西
1. 一张完整的超时预算表
没有这张表,所有“谁先超时”的讨论都是空的。
2. 一条请求的跨层时间线
最好是同一个 requestId 或 trace id,至少把客户端、网关、应用、下游四个点串起来。
3. 超时发生当时的等待证据
包括但不限于:
- 业务线程池
active/queue - 连接池
active/pending - 下游 RT 和错误分布
- 锁等待、事务时长、GC 停顿
这三样加在一起,才能把“谁先报错”变成“谁先花光预算”。
一个更像现场的判断方式:看预算耗在哪一段
还是上面的例子,为什么最后大家看到的是 504?因为预算是这么被吃掉的:
| 段落 | 预算消耗 |
|---|---|
| 网关转发 + 应用入口处理 | 30ms 左右 |
| 第一次库存调用等待 | 800ms |
| 重试切换与本地处理 | 40ms |
| 第二次库存调用等待 | 800ms |
| 收尾与响应写回 | 100ms 左右 |
加起来已经逼近 1.8 秒到 1.9 秒。此时客户端和网关谁先叫,只差几十毫秒,根本不值得单独争。真正有价值的结论是:应用内部给下游的等待和重试,把整条链的预算余量吃没了。
别急着一起调大 timeout,先做验证动作
很多团队一看到 504、应用 timeout、客户端超时同时出现,就会把所有 timeout 一起调大。这通常只会让等待链拖得更长。
我更愿意先做两种验证:
验证 1:去掉一段最可疑的预算消耗
比如关闭一次重试、临时降低某个下游调用频率、摘掉一段非核心串行调用。看的是:客户端超时、网关 504、应用 timeout 是否按顺序一起回落。
验证 2:对齐动作后的回落顺序
如果你切掉的是前段等待,常见回落顺序是:
- 应用内部 timeout 先掉。
- 长请求数减少。
- 网关 504 回落。
- 客户端报错跟着下降。
这个顺序比“有没有变好”更能说明问题。
如果你现在要往别处接
- 你还没分清 timeout 更像落在应用、网络还是下游,去看接口超时增多时,先区分应用、网络还是下游依赖?。
- timeout 已经被重试放大成调用量雪崩,去看重试风暴把慢接口进一步放大时,怎么区分表象和根因?。
- 你现在要解决的是参数设计,而不是先后归因,那就直接去 timeout / retry / 熔断参数那条线。
超时链里最会误导人的,一直都不是报错文案本身,而是谁拿着自己的秒表先喊了一句“我不等了”。
把预算表和时间线画出来之后,504、客户端超时、应用 timeout 往往不再是三个问题,而只是同一条等待链在不同位置上的三次表态。