Skip to content

Feign 是如何实现负载均衡的? ​

  • 标签:远程调用、微服务、Spring Cloud、后端、Feign

🏛 业务架构师 ​

Feign 的负载均衡实现一句话:Feign 本身不做 LB,它把"根据服务名拿一个实例"这件事委托给了 LoadBalancer(客户端负载均衡)。

拆开链路:

  1. @FeignClient(name = "inventory-service") 里的 name 被当作服务名(同时也是客户端 ID);
  2. Feign 发请求时,URL 中的服务名交给 Spring Cloud LoadBalancer(Ribbon 退役后的接棒者)的 ServiceInstanceListSupplier 解析;
  3. Supplier 从注册中心(Nacos)拉取该服务的实例列表(本地缓存 + 订阅变更推送);
  4. 按 负载均衡策略(默认轮询) 选出一个实例,替换服务名发起真实 HTTP 调用。

这带来几个架构上值得说的特性:

  • 客户端 LB 的优势:实例列表缓存在调用方本地,注册中心抖动不影响存量调用;能与熔断联动——某实例连续失败,LoadBalancer 按可用性过滤(availability filtering),自动"绕开"故障节点,比集中式 LB 的被动健康检查摘除快得多;
  • 负载均衡 + 熔断 + 重试的组合语义要小心:默认重试在某些版本与 LB 叠加会出现"重试换了实例"——资金类接口重试必须配幂等,这是我评审接口时的硬性检查项;
  • 定制点:通过配置更换策略(轮询/随机/基于 Nacos 权重),Nacos 权重 + 同集群优先还能实现"同城容灾路由"——元数据驱动的流量调度。

一句话:Feign 管声明与编解码,LoadBalancer + 注册中心管选址——职责分离本身就是这套架构的设计课。

🏗 系统架构师 ​

链路:@FeignClient(name="order-service") 的服务名 → Spring Cloud LoadBalancer(SCLB) 向注册中心查询实例列表 → 按算法选一个实例替换 URL 中的服务名 → 发起 HTTP 调用。这是客户端负载均衡。

要点(体现知识更新):

  1. Ribbon 已于 2020 年退役,SCLB 是官方替代:默认轮询,可换随机/自定义(ReactorLoadBalancer),按 service name 隔离配置(LoadBalancerClientFactory);
  2. 实例列表缓存:每次调用都查注册中心不可接受——SCLB 的 ServiceInstanceListSupplier 支持缓存(如 30s)加主动订阅(Nacos 2.x gRPC 推送),组合「发现组件的推送加 LB 的本地缓存」;
  3. K8s 场景可直接用 KubernetesServiceInstanceListSupplier 拿 Endpoints——LB 语义交给 kube-proxy 或保留客户端细粒度控制,二选一,别叠两层;
  4. 重试与 LB 联动:spring.cloud.loadbalancer.retry 加 Feign 重试器,注意幂等约束(GET 可重试,POST 默认不重试)。

架构本质:客户端 LB 把流量调度权交给消费方——每消费方独立熔断限流,故障不放大;代价是治理逻辑散在 SDK(这正是 mesh 要解决的问题)。

💻 全栈开发专家 ​

Feign 本身不做 LB,集成 Spring Cloud LoadBalancer 完成:

链路:代理方法调用 → LoadBalancer 的 ReactorServiceInstanceLoadBalancer → ServiceInstanceListSupplier 取实例列表 → 按策略选一个实例 → 用 ip:port 替换服务名拼真实 URL → HTTP 客户端发出。

实例列表来源:DiscoveryClientServiceInstanceListSupplier(从 Nacos/Consul 的 DiscoveryClient 拉取,带本地缓存,默认 35s 刷新,spring.cloud.loadbalancer.cache.ttl 可调),还可叠加过滤(健康、同 zone 优先)。

所以 Feign 的 LB 是客户端负载均衡:调用方本地决策,无集中 LB 单点,实例变化通过缓存刷新感知。

实践要点:

  1. 换策略:@LoadBalancerClient(value = "order-service", configuration = CustomLbConfig.class) 指定自定义负载器(新代码不再用 Ribbon 的 IRule)
  2. 缓存 TTL 与实例下线的感知延迟:滚动发布场景,实例下线后最长一个 TTL 内仍可能被选中——靠重试 + 熔断兜底,这也是发布瞬间小撮 502 的根因
  3. 重试要确认接口幂等:GET 重试没事,POST 盲重试会翻车