bigywater.tech
Spring · 136

超时叠加:Feign、Gateway 与下游各自的超时怎么叠

Gateway、Feign 和下游各自有超时,到底谁说了算?客户端超时之后下游会被取消吗?为什么客户端看到的是 500 不是 504?重试叠加以后一个请求会变成几个?这篇用三个服务把时间线逐个量出来。

504 之后还在干活
Gateway 3 秒超时返回 504 之后,B 仍然执行到 4.1 秒,没有被取消
5 次
Feign 开了 Retryer$Default,一个客户端请求让 B 被请求的次数
6.3 秒
Gateway Retry 叠加后客户端等到最终结果的时间

外层先超时

实测 · 本机执行输出
  [Gateway 3s < Feign 5s < B 4s?] 客户端看到 HTTP 504,耗时 3.1 秒;B 一共被请求 1 次(开始时刻 [0.1] 秒);B 的结束 [('B 结束(写出响应)', 4.1)];A 的日志 [('开始', 0.1), ('结束', 4.1)]

客户端拿 504,下游照样跑完。

内层先超时

实测 · 本机执行输出
  [Feign 2s < Gateway 3s < B] 客户端看到 HTTP 500,耗时 2.2 秒;B 一共被请求 1 次(开始时刻 [0.2] 秒);B 的结束 [('B 结束(写出响应)', 4.2)];A 的日志 [('开始', 0.2), ('失败', 2.2), ('结束', 2.2)]

谁先超时客户端看到谁的错误。

重试叠加

实测 · 本机执行输出
  [Feign 重试 5 次 × 1s] 客户端看到 HTTP 504,耗时 5.1 秒;B 一共被请求 5 次(开始时刻 [0.1, 1.3, 2.5, 3.8, 5.3] 秒);B 的结束 [('B 结束(写出响应)', 3.1), ('B 结束(写出响应)', 4.3), ('B 结束(写出响应)', 5.5), ('B 结束(写出响应)', 6.8), ('B 结束(写出响应)', 8.4)];A 的日志 [('开始', 0.1), ('失败', 6.3), ('结束', 6.4)]

总预算 = 单次超时 × 重试次数。

多层重试

实测 · 本机执行输出
  [Gateway Retry 2 次] 客户端看到 HTTP 500,耗时 6.3 秒;B 一共被请求 3 次(开始时刻 [0.1, 2.2, 4.2] 秒);B 的结束 [('B 结束(写出响应)', 4.1), ('B 结束(写出响应)', 6.2), ('B 结束(写出响应)', 8.2)];A 的日志 [('开始', 0.1), ('失败', 2.1), ('结束', 2.1), ('开始', 2.2), ('失败', 4.2), ('结束', 4.2), ('开始', 4.2), ('失败', 6.2), ('结束', 6.2)]

每一层的重试次数相乘。

分步演示

超时叠加
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。服务 B 是 Python 线程里的 HTTP 服务,/work?ms= 睡指定毫秒,记录每个请求的开始与结束;A 是带 Feign 客户端的 Spring Boot 应用;所有时刻是相对客户端发出请求那一刻的秒数,有 0.1 秒左右的抖动;B 的「结束」时刻是它写出响应的时刻,没有分辨客户端是否已断开
    • 没有验证:Feign 与 Gateway 的 connect-timeout;Resilience4j TimeLimiter 与取消信号;gRPC 的 deadline 传播(本站 gRPC 系列的相关文章);服务端对已断开连接的检测与取消;Gateway 的 Retry 过滤器对 POST 的行为;重试退避的具体间隔;把总超时预算通过请求头逐层传递的实现
    京ICP备2026067502号-1京公网安备11010502063487号