
Feign远程调用微服务通信、超时与重试机制微服务拆得欢调用火葬场。订单服务喊商品服务、商品服务叫库存服务这条调用链要是断了老板的血压也跟着飙。今天就聊聊微服务间怎么优雅地打电话——Feign远程调用。一、微服务间通信方式怎么选单体应用里方法调用直接new一个对象就完事了。拆成微服务后订单服务和商品服务跑在不同 JVM 里怎么通信先看三种主流方案通信方式优点缺点适用场景RestTemplate原生支持无额外依赖硬编码URL调用繁琐少量调用、简单场景Feign声明式、接口编程、自动负载均衡只支持HTTP协议RESTful服务间调用Dubbo高性能RPC、服务治理完善依赖Dubbo协议生态偏重高并发内部调用1.1 为什么选 FeignRestTemplate 每次调用都得拼 URL“http://product-service/api/product/” id服务名写死不说参数拼接还容易出错。Dubbo 强是强但咱团队要是全栈 HTTP 微服务体系Feign 就是最自然的选择——它让你用接口调用的方式写 HTTP 请求代码跟本地调用一样简洁。二、OpenFeign声明式HTTP客户端OpenFeign 的核心思想就四个字面向接口。你只需要定义一个 Java 接口用注解描述 HTTP 请求的细节Feign 在运行时帮你生成代理对象实际完成 HTTP 调用。订单服务(consumer) 商品服务(provider) ┌─────────────┐ ┌─────────────┐ │ ProductFeign │ ───HTTP请求──→ │ /api/product │ │ .getById() │ │ Controller │ └─────────────┘ └─────────────┘ ↑ 代理对象 ↑ Nacos注册 └── Feign动态生成 └── 服务发现2.1 SpringBoot 整合 OpenFeignStep 1引入依赖dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependency!-- 需要搭配负载均衡 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-loadbalancer/artifactId/dependencyStep 2启动类加注解SpringBootApplicationEnableFeignClients// 开启Feign扫描会自动扫描FeignClient接口publicclassOrderApplication{publicstaticvoidmain(String[]args){SpringApplication.run(OrderApplication.class,args);}}EnableFeignClients负责扫描所有标注了FeignClient的接口在 Spring 容器中生成动态代理 Bean。Step 3定义 Feign 接口FeignClient(nameproduct-service)// name 对应 Nacos 中的服务名publicinterfaceProductFeignClient{GetMapping(/api/product/{id})ProductDTOgetById(PathVariable(id)Longid);PostMapping(/api/product/list)ListProductDTOlistByIds(RequestBodyListLongids);}就这样你可以在 Service 里像调用本地方法一样调用远程接口ServicepublicclassOrderService{AutowiredprivateProductFeignClientproductFeignClient;publicOrderDTOcreateOrder(LongproductId){ProductDTOproductproductFeignClient.getById(productId);// ... 后续业务逻辑}}2.2 Feign 如何做到自动负载均衡Feign 内部集成了 LoadBalancerSpringCloud 2020 后替代 Ribbon。当你调用productFeignClient.getById(1L)时Feign 根据name product-service去 Nacos 拉取product-service的所有实例列表比如三个节点192.168.1.10:8081、192.168.1.11:8081、192.168.1.12:8081LoadBalancer 按策略默认轮询选出一个实例拼接真实 URL 发起 HTTP 请求整个过程对开发者完全透明你只管写接口不用管 URL。三、Feign 配置超时时间和日志3.1 超时配置重点默认超时很短连接超时 2 秒读超时 5 秒。生产环境必须按需调整spring:cloud:openfeign:client:config:product-service:# 针对特定服务配置connect-timeout:5000# 连接超时 5 秒read-timeout:10000# 读取超时 10 秒default:# 全局默认配置connect-timeout:3000read-timeout:5000connectTimeout和readTimeout的区别connect-timeout三次握手建立 TCP 连接的最长等待时间。网络不好、对方服务宕机时会触发。read-timeout连接建立后等待服务端返回响应数据的最长时间。适合处理慢接口比如导出报表。注意如果 Feign 集成了 Ribbon超时配置得写在 Ribbon 下LoadBalancer 则直接认 Feign 配置。3.2 日志级别调试时很有用能看清 Feign 发了什么请求logging:level:com.example.feign.ProductFeignClient:DEBUG# 接口级别日志spring:cloud:openfeign:client:config:default:logger-level:FULL# NONE/BASIC/HEADERS/FULL级别输出内容NONE不打印日志默认BASIC请求方法、URL、响应状态码、耗时HEADERSBASIC 请求和响应头FULLHEADERS 请求体和响应体生产慎用四、Feign 重试机制网络抖动、服务短暂不可用是常态重试能大幅提升调用成功率。ConfigurationpublicclassFeignConfig{BeanpublicRetryerfeignRetryer(){// period100ms, maxPeriod1s, maxAttempts3// 首次重试等待100ms之后倍增最多重试3次含首次调用returnnewRetryer.Default(100,TimeUnit.SECONDS.toMillis(1),3);}}重试策略说明period首次重试间隔maxPeriod最大重试间隔倍增增长到这个值后不再增加maxAttempts最大尝试次数1 表示不重试重试一定要考虑幂等性像创建订单这种非幂等操作重试可能导致重复下单。通常只对 GET 请求或明确幂等的 POST 接口开启重试。五、Feign 拦截器Token 透传微服务架构中用户 Token 通常由网关校验后透传给下游。Feign 调用时如果丢了 Token下游服务就不知道谁在操作。解决方案RequestInterceptorComponentpublicclassFeignTokenInterceptorimplementsRequestInterceptor{Overridepublicvoidapply(RequestTemplatetemplate){// 从当前请求上下文中获取 TokenServletRequestAttributesattributes(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();if(attributes!null){Stringtokenattributes.getRequest().getHeader(Authorization);if(StringUtils.hasText(token)){template.header(Authorization,token);}}}}原理很简单Feign 每次发起 HTTP 请求前都会调用所有RequestInterceptor的apply方法你可以在里面往请求头上塞任何东西——Token、TraceId、版本号等。六、Feign 降级出错了咋办远程调用总有失败的时候不能把异常直接抛给用户看。FallbackFactory 提供了容错兜底的能力。ComponentpublicclassProductFallbackFactoryimplementsFallbackFactoryProductFeignClient{OverridepublicProductFeignClientcreate(Throwablecause){log.error(产品服务调用失败,cause);returnnewProductFeignClient(){OverridepublicProductDTOgetById(Longid){// 返回兜底数据ProductDTOfallbacknewProductDTO();fallback.setId(id);fallback.setName(商品信息获取失败);returnfallback;}};}}// 在 FeignClient 中引用FeignClient(nameproduct-service,fallbackFactoryProductFallbackFactory.class)publicinterfaceProductFeignClient{...}FallbackFactory比fallback多了一个好处能拿到具体的异常信息Throwable cause方便记录日志和区分不同错误原因。Feign Sentinel 熔断降级把fallbackFactory换成 Sentinel 的fallback类就能获得更强大的熔断能力——比如慢调用比例熔断、异常比例熔断。不过配置相对复杂一般团队直接在 Feign 层兜底就够用了。七、常见踩坑7.1 GET 请求多参数传递// 错误写法GET 请求用 RequestBodyGetMapping(/api/product/search)ListProductDTOsearch(RequestBodySearchDTOdto);// ❌ 会报错// 正确写法1逐个参数GetMapping(/api/product/search)ListProductDTOsearch(RequestParamStringkeyword,RequestParamIntegercategoryId);// 正确写法2参数展开Spring 会自动把对象字段转为 query stringGetMapping(/api/product/search)ListProductDTOsearch(SpringQueryMapSearchDTOdto);// ✅7.2 POST 请求对象传参// POST 请求 RequestBody 没问题但注意PostMapping(/api/product)ResultProductDTOcreate(RequestBodyProductSaveDTOdto);// 被调用方服务 Controller 必须加 RequestBody否则接收不到数据7.3 编码问题URL 中有中文参数时Feign 默认编码可能出问题。加上如下配置BeanpublicRequestInterceptorencodingInterceptor(){returntemplate-{// 强制使用 UTF-8 编码查询参数template.queryParams().forEach((k,v)-{v.forEach(value-{// 已自动处理此处仅示意});});};}更简单的做法URL 参数做一次URLEncoder.encode(value, UTF-8)。总结Feign 把 HTTP 调用变成了面向接口编程配合 Nacos 服务发现和 LoadBalancer 负载均衡形成了一套完整、简洁的微服务通信体系。搞定超时配置、重试策略、Token 透传、降级兜底这几个关键点你的微服务调用链就能跑得稳稳当当。