Spring Cloud OpenFeign:超时和重试要一起设计
一、远程调用默认值不能直接进生产
Spring Cloud OpenFeign 让服务间调用像本地接口一样方便,但远程调用不是本地调用。网络会抖,下游会慢,连接池会耗尽,响应也可能部分失败。如果超时和重试没有设计,Feign 会把问题放大。
超时太长会拖垮线程池,超时太短会误伤正常请求。重试太多会放大流量,完全不重试又会降低临时故障下的成功率。两者必须一起看。
超时配置还要考虑连接超时和读取超时的区别。连接超时通常设短一些,因为它主要反映网络连通性或服务是否启动;读取超时可以稍长,因为服务可能需要时间处理复杂请求。两者混淆是常见的配置错误。
二、调用链要有时间预算
flowchart TD A[入口请求 2s预算] --> B[服务 A] B --> C[Feign 调用 B 500ms] C --> D[Feign 调用 C 300ms] D --> E[响应返回]一个入口请求有总预算,下游调用只能分配其中一部分。不能每个 Feign 客户端都配置 3 秒超时,否则链路上几个调用叠加,用户请求早就超时了。
时间预算要从入口向下传递。可以通过 context、header 或框架封装,让下游知道剩余时间。剩余时间不足时,直接降级比继续调用更合理。
超时传递的实现可以基于 Spring Cloud Sleuth 或 Micrometer 的上下文传播。在入口处记录截止时间,后续每个 Feign 调用前检查剩余时间,如果不足预设阈值就直接降级。这样能避免请求在接近超时时才失败,浪费资源。
if (remainingTime < minimumAcceptableTime) { throw new TimeBudgetExhaustedException(); }三、配置要按下游区分
feign: client: config: inventory: connectTimeout: 100 readTimeout: 300 payment: connectTimeout: 200 readTimeout: 800不同下游不能共用一个超时。库存查询、支付确认、用户画像、推荐服务,对延迟和可靠性的要求不同。配置要按业务语义拆开。
@FeignClient(name = "inventory", fallbackFactory = InventoryFallbackFactory.class) public interface InventoryClient { @GetMapping("/stock/{skuId}") StockView getStock(@PathVariable String skuId); }fallback 不要假装成功。库存查询失败可以返回“未知”,支付失败不能返回“已成功”。降级语义必须和业务一致。
四、重试要避免放大故障
重试只适合幂等、短暂、可恢复的错误。写操作、支付、扣库存这类调用,如果没有幂等键,不应该随便重试。即使可重试,也要加退避和最大次数。
还要结合熔断。下游持续失败时,继续重试只会增加压力。熔断后快速失败,给系统恢复空间。Feign、Resilience4j、网关和线程池策略要统一,不要多层重复重试。
连接池也要匹配。Feign 底层使用的 HTTP 客户端如果连接池太小,会造成排队;太大又可能压垮下游。每个下游的最大连接、空闲连接和连接存活时间,都应根据流量和下游容量设置。
还要记录调用维度指标。按 Feign client、方法、状态码、异常类型、耗时分位数统计,才能知道哪个下游不稳定。只看入口接口错误率,很难定位远程调用问题。
请求头透传要克制。traceId、租户、用户权限摘要可以传,但不要把大量上下文塞进 header。header 过大可能导致网关或下游拒绝,也会增加网络成本。
最后,降级要有产品语义。推荐服务失败可以返回空推荐,库存服务失败要提示未知,支付服务失败必须明确失败或处理中。技术 fallback 不能脱离业务含义。
Feign 客户端的测试也很重要。可以用 WireMock 或 OkHttp mock 来测试超时、重试、熔断和降级行为。很多团队只测试正常流程,上线后发现超时配置根本没生效,或者重试逻辑导致重复下单。测试覆盖远程调用异常场景,比测试正常流程更有价值。
五、总结
Spring Cloud OpenFeign 的超时和重试要基于入口时间预算、下游语义和幂等性设计,并配合 fallback 与熔断。
远程调用看起来像本地接口,但故障语义完全不同。配置越随意,事故时放大越快。