bigywater.tech
Spring · 120

Feign 的超时、重试与错误解码

Feign 的默认超时是多少?超时之后会重试吗?POST 会不会被重复提交?404 怎么变成返回值而不是异常?这篇在 Spring Boot 4.0.8、Spring Cloud OpenFeign 5.0.3 上用一个会睡觉、会返回 503 和 404 的后端逐项实测并统计后端收到的请求数。

POST 也重试
把 Retryer 换成 feign.Retryer$Default 之后,读超时的 POST 后端收到 5 次相同的请求
dismiss404
属性名是 dismiss404;404 响应体是错误 JSON 时反而会因为解码成返回类型失败
RetryableException
读超时抛的异常类型,即使默认并不重试

读超时

实测 · 本机执行输出
  不配任何超时,GET /slow?ms=3000:后端收到 1 次请求,调用方耗时 3.1 秒 → OK slow-done
  read-timeout=1000(毫秒),GET /slow?ms=3000:后端收到 1 次请求,调用方耗时 1.0 秒 → EX feign.RetryableException (cause SocketTimeoutException): Read timed out executing GET http://127.0.0.1:65429/slow?ms=3000
  default.read-timeout=1000 但 orders.read-timeout=5000,GET /slow?ms=3000:后端收到 1 次请求,调用方耗时 3.1 秒 → OK slow-done

client.config 下的 read-timeout 毫秒;按客户端名覆盖 default;超时异常是 RetryableException。

重试

实测 · 本机执行输出
  默认 Retryer,读超时,GET:后端收到 1 次请求,调用方耗时 1.0 秒 → EX feign.RetryableException (cause SocketTimeoutException): Read timed out executing GET http://127.0.0.1:65429/slow?ms=3000
  retryer=feign.Retryer$Default,读超时,GET:后端收到 5 次请求,调用方耗时 6.3 秒 → EX feign.RetryableException (cause SocketTimeoutException): Read timed out executing GET http://127.0.0.1:65429/slow?ms=3000
  同上,读超时,POST:后端收到 5 次请求,调用方耗时 6.2 秒 → EX feign.RetryableException (cause SocketTimeoutException): Read timed out executing POST http://127.0.0.1:65429/slow?ms=3000
  同上,503 + Retry-After: 1:后端收到 5 次请求,调用方耗时 4.0 秒 → EX feign.RetryableException (cause ServiceUnavailable): [503 Service Unavailable] during [GET] to [http://127.0.0.1:65429/status/503] [OrdersApi#status(int)]: [busy]
  同上,500:后端收到 1 次请求,调用方耗时 0.0 秒 → EX FeignException$InternalServerError: [500 Internal Server Error] during [GET] to [http://127.0.0.1:65429/status/500] [OrdersApi#status(int)]: [boom]
  默认 Retryer,503 + Retry-After: 1:后端收到 1 次请求,调用方耗时 0.0 秒 → EX feign.RetryableException (cause ServiceUnavailable): [503 Service Unavailable] during [GET] to [http://127.0.0.1:65429/status/503] [OrdersApi#status(int)]: [busy]
  默认 Retryer,500:后端收到 1 次请求,调用方耗时 0.0 秒 → EX FeignException$InternalServerError: [500 Internal Server Error] during [GET] to [http://127.0.0.1:65429/status/500] [OrdersApi#status(int)]: [boom]

默认永不重试;Retryer$Default 对读超时的 GET、POST 和带 Retry-After 的 503 重试 5 次,500 不重试。

错误解码

实测 · 本机执行输出
  默认,返回类型是 Item,后端 404:后端收到 1 次请求,调用方耗时 0.0 秒 → EX FeignException$NotFound: [404 Not Found] during [GET] to [http://127.0.0.1:65429/status/404] [OrdersApi#notFound()]: [{"error":"no such order"}]
  默认,返回类型是 ResponseEntity<Item>,后端 404:后端收到 1 次请求,调用方耗时 0.0 秒 → EX FeignException$NotFound: [404 Not Found] during [GET] to [http://127.0.0.1:65429/status/404] [OrdersApi#notFoundEntity()]: [{"error":"no such order"}]
  dismiss404=true,返回类型是 Item:后端收到 1 次请求,调用方耗时 0.1 秒 → EX feign.codec.DecodeException (cause RestClientException): Error while extracting response for type [class demo.Main$Item] and content type [application/json]
  dismiss404=true,返回类型是 ResponseEntity<Item>:后端收到 1 次请求,调用方耗时 0.0 秒 → EX feign.codec.DecodeException (cause RestClientException): Error while extracting response for type [class demo.Main$Item] and content type [application/json]
  dismiss404=true,404 的响应体为空,返回类型是 Item:后端收到 1 次请求,调用方耗时 0.0 秒 → OK null
  dismiss404=true,404 的响应体为空,返回类型是 ResponseEntity<Item>:后端收到 1 次请求,调用方耗时 0.0 秒 → OK <404 NOT_FOUND Not Found,[content-length:"0", content-type:"text/plain", date:"Thu, 22 Oct 2026 01:03:33 GMT", server:"BaseHTTP/0.6 Python/3.14.8"]>
  自定义 ErrorDecoder(404 转成 IllegalStateException):后端收到 1 次请求,调用方耗时 0.0 秒 → EX java.lang.IllegalStateException: 订单不存在(自定义 ErrorDecoder 转换的,方法 OrdersApi#notFound())
  同上,500:后端收到 1 次请求,调用方耗时 0.0 秒 → EX FeignException$InternalServerError: [500 Internal Server Error] during [GET] to [http://127.0.0.1:65429/status/500] [OrdersApi#status(int)]: [boom]

404 默认抛异常;dismiss404 把响应体当返回类型解码;error-decoder 属性配置自定义解码。

分步演示

Feign 超时重试
本步的对象

    本文用到的工具与边界

    • 本机 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 解析;数字每次运行会有小幅抖动。后端是 Python 线程里的 HTTP 服务:/slow 睡指定毫秒、/status/503 返回 503 与 Retry-After: 1、/status/500、/status/404(带 JSON 响应体)、/status/404e(空响应体),并统计每个路径收到的请求次数;所有配置都是 spring.cloud.openfeign.client.config.* 属性;耗时是调用方看到的整体耗时,会有零点几秒的抖动
    • 没有验证:connect-timeout(本机没有可用的不可达地址来稳定地测);重试的退避间隔具体是多少(只看到 5 次的总耗时 6.3 秒);Retryer$Default 之外的自定义 Retryer;Spring Cloud CircuitBreaker 包装 Feign;Feign 与 LoadBalancer 重试(spring.cloud.loadbalancer.retry)叠加时的次数;@FeignClient 注解上的 dismiss404 写法;ErrorDecoder 的 RetryAfter 与自定义异常的重试语义
    京ICP备2026067502号-1京公网安备11010502063487号