Spring Cloud LoadBalancer 是 Spring 官方推出的一款轻量级客户端负载均衡器,主要用来替代已经停更的 Netflix Ribbon。
从 Spring Cloud 2020.0 版本开始,Ribbon 已经被正式移除,Spring Cloud LoadBalancer 成为了默认的负载均衡组件。
在 Spring Cloud 中使用 LoadBalancer 非常简单,通常只需要引入依赖 并配合服务发现(如 Nacos、Eureka、Consul)一起使用。
以下是具体的使用指南:
1. 引入依赖
首先,在你的 pom.xml 中引入 Spring Cloud LoadBalancer 的依赖。
注意:如果你引入了 Spring Cloud Alibaba Nacos 或者 Eureka 的服务发现依赖,部分新版本已经默认包含了 LoadBalancer,但显式引入最为稳妥。
XML
<!-- 引入 Spring Cloud LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 还需要搭配一个注册中心,例如 Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2. 三种常见的调用方式
Spring Cloud LoadBalancer 可以在底层无缝对接你常用的 HTTP 客户端进行远程服务调用。
方式一:配合 RestTemplate 使用(最常见)
只需要在注入 RestTemplate 的 Bean 上面加一个 @LoadBalanced 注解即可开启负载均衡。
java
@Configuration
public class RestConfig {
@Bean
@LoadBalanced // 这个注解让 RestTemplate 具备了通过服务名查找 IP 列表并负载均衡的能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
发起调用 :在调用时,直接使用服务名(而不是硬编码的 IP 和端口)。
java
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order")
public String createOrder() {
// "stock-service" 是目标服务在注册中心的名字
String url = "http://stock-service/stock/deduct";
return restTemplate.getForObject(url, String.class);
}
}
方式二:配合 OpenFeign 使用(最推荐)
在现代微服务开发中,通常推荐使用声明式的 OpenFeign。
在新版 Spring Cloud 中,OpenFeign 默认已经整合了 Spring Cloud LoadBalancer,你几乎不需要做额外配置。
java
// 声明 Feign 客户端,指定服务名
@FeignClient(name = "stock-service")
public interface StockFeignClient {
@GetMapping("/stock/deduct")
String deduct();
}
调用时直接像调本地方法一样使用即可,底层自动实现负载均衡。
方式三:配合 WebClient 使用(响应式编程)
如果你的项目基于 WebFlux,可以使用 WebClient。
java
@Configuration
public class WebClientConfig {
@Bean
@LoadBalanced
public WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
}
3. 自定义负载均衡策略
Spring Cloud LoadBalancer 默认采用的是轮询策略(RoundRobin)。
如果你想把它修改为随机策略(Random)或自定义算法,可以按照以下步骤配置:
第一步:创建一个配置类
避坑指南 :这个配置类上千万不要 加
@Configuration注解,或者确保它不在@SpringBootApplication扫描的包路径下,否则该策略会变成全局生效,引起不必要的冲突。
java
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
// 返回随机策略的负载均衡器
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
第二步:使用 @LoadBalancerClient 绑定该策略
在启动类或任意一个 Spring 扫描得到的 @Configuration 类上,通过注解指定对哪个服务生效。
java
@SpringBootApplication
// name = 目标服务名,configuration = 自定义的策略类
@LoadBalancerClient(name = "stock-service", configuration = CustomLoadBalancerConfig.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
4. 性能优化:缓存机制 (Cache)
每次发起调用时,LoadBalancer 如果都去注册中心拉取服务列表会非常耗时。因此,LoadBalancer 在本地维护了一份缓存。
默认缓存过期时间为 35秒。如果你的服务实例经常上下线,或者对服务的感知延迟要求很高,可以通过 application.yml 进行调整:

5、Feign的重试机制
在 Spring Cloud 环境下,OpenFeign 默认是关闭重试机制的(底层使用的是 Retryer.NEVER_RETRY)。
如果调用超时,它会直接抛出异常。
要开启重试,目前主要有两种方式:
- Spring Cloud LoadBalancer 重试(推荐)
- OpenFeign 原生重试。
方式一:使用 LoadBalancer 重试(微服务架构中最推荐)
这种方式不仅能重试,还能在不同实例之间切换。
比如 A 实例挂了或超时了,它会自动去请求 B 实例。
1. 引入依赖
必须引入 spring-retry 依赖,LoadBalancer 的重试机制才会生效。
XML
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
2. 在 YAML 中配置重试策略 
按上述配置,如果首次请求失败,它不会在当前实例重试,而是直接换一个新实例重试 1 次,总共最多请求 2 次。
方式二:使用 OpenFeign 原生重试
这种方式是 Feign 框架自己带的,它只是单纯地重新发起 HTTP 请求,不具备感知实例下线并切换下一个节点的能力,因此在配合注册中心使用时不如上面的方式灵活。
通过配置类注入 Retryer 即可开启:
java
import feign.Retryer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Retryer feignRetryer() {
// 参数1: 初始重试间隔 (100ms)
// 参数2: 最大重试间隔 (1000ms)
// 参数3: 最大尝试次数 (3次,包含首次调用,即实际重试 2 次)
return new Retryer.Default(100, 1000, 3);
}
}
(然后在 @FeignClient(name = "stock-service", configuration = FeignConfig.class) 中绑定该配置)
🚨 重试机制的致命坑点(避坑指南)
开启重试非常简单,但如果没有处理好以下几个坑,可能会引发严重的线上故障。
坑一:Read Timeout 引发的非幂等性问题(最容易引发生产事故)
超时分为 Connect Timeout(连接超时)和 Read Timeout(读取超时)。
-
Connect Timeout :请求根本没发出去,或者没连上目标服务器。此时重试绝对安全。
-
Read Timeout :请求已经到达目标服务器并且正在执行,只是因为数据库慢或者逻辑复杂,没在规定时间内返回结果给客户端。
致命场景 :订单服务调用库存服务执行 POST /stock/deduct(扣减库存)。库存服务其实扣减成功了,但由于网络抖动耗时 6 秒,超过了 Feign 配置的 5 秒 readTimeout。订单服务认为失败了,触发重试 ,结果库存被扣了两次!
解决方案:
-
接口必须保证幂等性 。所有写入类操作(POST/PUT),必须通过唯一流水号(单据号/Token)+ 数据库唯一索引/分布式锁来防止重复提交。
-
强烈建议
spring.cloud.loadbalancer.retry.retry-on-all-operations保持为false。默认只对GET(天然幂等)请求进行重试。
坑二:重试风暴导致雪崩
如果下游的 stock-service 是因为流量过大导致 CPU 打满而大面积超时。
此时上游的 order-service 开启了重试,原本 1000 的 QPS 会瞬间被放大成 3000 QPS(假设重试 2 次)。这不仅救不了下游,反而会成为压死骆驼的最后一根稻草,导致整个微服务集群雪崩。
解决方案:
重试机制绝不能孤立使用,必须配合熔断器(如 Resilience4j 或 Sentinel)。
当错误率达到一定阈值(比如 50%),直接触发熔断,拒绝后续请求并快速失败(Fallback),而不是继续傻傻地重试。
坑三:LoadBalancer 重试与 Feign 原生重试的冲突
有些开发者在网上的老教程里复制了 Feign 的 Retryer.Default 代码,同时又在 YAML 里配置了 Spring Cloud LoadBalancer 的重试。
后果 :两者会产生嵌套放大效应。假如 Feign 配置重试 3 次,LoadBalancer 配置重试 3 次,一个超时的请求最终可能会被发出去 3*3=9 次。
解决方案:永远只用一种。
推荐彻底放弃 Feign 自带的 Retryer(保持其默认的 NEVER_RETRY),统一通过 spring-retry + LoadBalancer 在 YAML 中进行全局管控。
坑四:超时时间设置不合理
重试是需要消耗总响应时间的。
假设你配置了 readTimeout=5000(5秒),并允许重试 2 次。那么客户端线程最多会被阻塞 15 秒(5s * 3次)。
如果你的服务对接了前端或网关,网关的超时时间如果是 10 秒,那么在微服务完成重试前,网关就已经给用户返回 504 Gateway Timeout 了,你的重试毫无意义。
解决方案:
确保超时时间的递进关系:网关超时时间 > 消费者总超时时间(包含重试) > 生产者超时时间 。如果业务极慢,应该改用 MQ 异步处理,而不是靠拉长超时时间和重试来硬抗。
6、OpenFeign的Fallback 机制
场景:
如果 Feign 远程调用失败或超时,我不想抛出异常,而是想返回一个默认的降级数据(兜底数据),OpenFeign 的 Fallback 机制具体要怎么实现?
在 OpenFeign 中实现 Fallback(服务降级/兜底)非常成熟。
Feign 本身将降级功能委托给了底层的熔断器(如 Resilience4j 或 Alibaba Sentinel)。
实现降级主要有两种方式:
fallback(简单兜底)fallbackFactory(带异常信息的兜底)。
在实际开发中,强烈推荐使用 fallbackFactory ,因为它能捕获并记录导致降级的具体异常。
以下是完整的落地步骤:
1. 开启熔断与降级支持 (最容易漏掉的一步)
在现代版本的 Spring Cloud 中,Feign 的降级功能默认是关闭的。
必须引入熔断器依赖,并在配置文件中显式开启。
引入依赖
(如果你使用的是 Spring Cloud Alibaba 体系,引入 Sentinel 即可;如果是标准 Spring Cloud,引入 Resilience4j):
XML
<!-- 方案 A:标准 Spring Cloud 熔断器 (Resilience4j) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<!-- 方案 B:Spring Cloud Alibaba Sentinel (推荐) -->
<!-- <dependency> -->
<!-- <groupId>com.alibaba.cloud</groupId> -->
<!-- <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> -->
<!-- </dependency> -->
修改 application.yml 开启配置:

2. 方式一:使用 fallbackFactory(推荐)
FallbackFactory 的最大优势是:
能够拿到触发降级的具体异常(Throwable),非常有利于排查问题(比如到底是超时了,还是被限流了,还是目标服务报 500 了)。
编写 FallbackFactory 类:
实现 org.springframework.cloud.openfeign.FallbackFactory 接口,并必须加上 @Component 交给 Spring 容器管理 。
java
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
@Component // 必须注入 Spring 容器
public class StockClientFallbackFactory implements FallbackFactory<StockFeignClient> {
@Override
public StockFeignClient create(Throwable cause) {
// cause 就是导致调用的异常对象
return new StockFeignClient() {
@Override
public String deductStock() {
// 1. 记录具体的失败原因
System.err.println("调用库存服务扣减接口失败,触发降级!原因:" + cause.getMessage());
// 2. 返回兜底数据
return "触发兜底机制:当前访问人数过多,请稍后再试!";
}
};
}
}
在 @FeignClient 中绑定:
java
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(
name = "stock-service",
fallbackFactory = StockClientFallbackFactory.class // 指向 Factory 类
)
public interface StockFeignClient {
@GetMapping("/stock/deduct")
String deductStock();
}
3. 方式二:使用 fallback(简单粗暴)
如果你根本不在乎是什么原因导致的失败,只需要一个静态的兜底返回值,可以直接使用 fallback。
编写 Fallback 类:
直接实现你的 Feign 客户端接口,并加上 @Component。
java
import org.springframework.stereotype.Component;
@Component // 必须注入 Spring 容器
public class StockClientFallback implements StockFeignClient {
@Override
public String deductStock() {
return "触发简单兜底:服务暂时不可用";
}
}
在 @FeignClient 中绑定:
java
@FeignClient(
name = "stock-service",
fallback = StockClientFallback.class // 指向 Fallback 类
)
public interface StockFeignClient {
@GetMapping("/stock/deduct")
String deductStock();
}
🚨 降级机制的常见避坑指南
-
fallback和fallbackFactory不要同时使用如果在一个
@FeignClient中同时配置了这两个属性,fallbackFactory的优先级更高,fallback会被直接忽略。 -
ErrorDecoder 与 Fallback 的冲突
如果你按照之前说的自定义了
ErrorDecoder,并且在里面抛出了自定义的RemoteCallException。别担心,这依然会触发 Fallback 机制。在fallbackFactory的cause参数里,你拿到的正是你自定义抛出的那个RemoteCallException,你可以通过cause.getCode()来做更细粒度的降级逻辑分发。 -
Fallback 对象未注入 Spring 容器
这是最常见的新手错误。无论是 Fallback 类还是 FallbackFactory 类,如果没有标
@Component或@Service,Feign 在启动时就会报错找不到对应的 Bean。 -
内部服务间的异常传递
如果你不希望触发 Fallback 默默消化掉错误,而是想把下游服务明确的业务报错(例如 "库存不足")透传给上游,那就不要配置
fallback,直接利用ErrorDecoder抛出异常并由上游的全局异常处理器(@RestControllerAdvice)拦截并返回给前端即可。Fallback 只适合做"非核心链路"的柔性可用保障(比如获取用户头像失败,降级返回默认头像)。