Spring Cloud LoadBalancer
导学
"Ribbon 进入维护模式了,CTO 大翔让我评估一下替代方案。"白歌在周会上说。
小崔有点紧张:"Ribbon 我用得挺好的啊,为什么要换?"
白歌解释道:"Ribbon 基于阻塞 I/O 线程模型,在高并发场景下线程开销大。Spring Cloud 官方推出的 LoadBalancer 基于 Reactor 响应式架构,是 Ribbon 的继任者。而且从 Spring Cloud 2020.0.x 开始,如果项目中不显式引入 Ribbon,默认就会使用 LoadBalancer。"
定位与问题场景
Ribbon 的核心问题:基于 Servlet 阻塞模型,每个请求占用一个线程,在微服务间高频调用场景下线程上下文切换开销大。Spring Cloud LoadBalancer 通过响应式架构解决了这个问题:
| 维度 | Ribbon | Spring Cloud LoadBalancer |
|---|---|---|
| 线程模型 | 阻塞(Servlet) | 响应式(Reactor) |
| 维护状态 | 已进入维护模式 | 官方活跃维护 |
| Spring 集成 | 通过拦截器适配 | 原生 Spring 生态 |
| 自定义策略 | 实现 IRule 接口 | 实现 ReactorLoadBalancer |
| 缓存 | 无内置缓存 | 内置 LoadBalancerCacheManager |
Spring Cloud LoadBalancer 核心原理
架构对比
核心组件
| 组件 | 职责 |
|---|---|
ReactorLoadBalancer<T> | 响应式负载均衡器接口,T 为实例类型 |
RoundRobinLoadBalancer | 默认轮询实现 |
RandomLoadBalancer | 随机实现 |
ServiceInstanceListSupplier | 响应式获取服务实例列表(从 DiscoveryClient) |
LoadBalancerClientFactory | 创建和管理每个服务的 LoadBalancer 实例 |
负载均衡流程
完整示例:飞翔科技订单服务迁移到 LoadBalancer
场景描述
将订单服务从 Ribbon 迁移到 Spring Cloud LoadBalancer,保持相同的负载均衡能力。
操作前后对比:
| 维度 | 迁移前(Ribbon) | 迁移后(LoadBalancer) |
|---|---|---|
| 依赖 | spring-cloud-starter-netflix-ribbon | spring-cloud-starter-loadbalancer |
| 配置方式 | Ribbon 专属配置 | Spring 原生 @Bean |
| 调用方式 | RestTemplate + @LoadBalanced | RestTemplate + @LoadBalanced(兼容) |
| 自定义策略 | IRule | ReactorLoadBalancer |
步骤一:依赖切换
<!-- 移除 Ribbon -->
<!-- <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency> -->
<!-- 使用 LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
步骤二:RestTemplate 配置(向后兼容)
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // LoadBalancer 拦截器自动生效
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
无需修改任何调用代码——RestTemplate + @LoadBalanced 注解保持不变。
步骤三:自定义随机负载均衡策略
public class CustomRandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final String serviceId;
private final ObjectProvider<ServiceInstanceListSupplier> suppliers;
public CustomRandomLoadBalancer(
String serviceId,
ObjectProvider<ServiceInstanceListSupplier> suppliers) {
this.serviceId = serviceId;
this.suppliers = suppliers;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = suppliers.getIfAvailable();
return supplier.get(request)
.collectList()
.map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 随机选择一个实例
int index = ThreadLocalRandom.current()
.nextInt(instances.size());
return new DefaultResponse(instances.get(index));
});
}
}
@Configuration
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> customLoadBalancer(
Environment environment,
LoadBalancerClientFactory factory) {
String serviceId = environment.getProperty(
LoadBalancerClientFactory.PROPERTY_NAME);
return new CustomRandomLoadBalancer(
serviceId,
factory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class)
);
}
}
步骤四:响应式 WebClient 调用(高级用法)
@RestController
public class OrderController {
@Autowired
private WebClient.Builder webClientBuilder;
// LoadBalancer + WebClient(响应式,推荐新项目使用)
@GetMapping("/order/check-stock-reactive/{productId}")
public Mono<Map> checkStockReactive(@PathVariable Long productId) {
return webClientBuilder.build()
.get()
.uri("http://FEIXIANG-INVENTORY-SERVICE/inventory/" + productId)
.retrieve()
.bodyToMono(Map.class);
}
}
易错场景
1. Spring Cloud 2020.x 之前引入 LoadBalancer 导致 Ribbon 失效
Spring Cloud Hoxton 及更早版本默认使用 Ribbon。如果手动引入 spring-cloud-starter-loadbalancer,需要排除 Ribbon 的自动配置:
spring:
cloud:
loadbalancer:
ribbon:
enabled: false
2. 自定义 LoadBalancer 未注册导致 NoInstanceAvailableException
自定义的 ReactorLoadBalancer Bean 必须被 Spring 容器管理,且 Bean Name 必须遵循约定:
@LoadBalancerClient(
name = "FEIXIANG-INVENTORY-SERVICE",
configuration = CustomLoadBalancerConfig.class
)
3. WebClient 与 RestTemplate 混用时 LoadBalancer 不一致
@LoadBalanced 注解对 RestTemplate 生效,但不影响 WebClient。使用 WebClient 时需通过 @LoadBalanced WebClient.Builder 自动注入。
4. 滥用缓存导致扩缩容感知延迟
LoadBalancer 默认启用缓存,实例列表不会每次都去注册中心拉取。如果频繁扩缩容,需调整缓存 TTL:
spring:
cloud:
loadbalancer:
cache:
ttl: 10s # 缓存 10 秒
capacity: 256
面试考点
Spring Cloud LoadBalancer 与 Ribbon 的区别?
- LoadBalancer 基于 Reactor 响应式架构,Ribbon 基于阻塞线程模型
- LoadBalancer 是 Spring 官方维护,Ribbon 已停更
- LoadBalancer 与 Spring 生态(WebClient、Reactive)深度集成
- LoadBalancer 内置缓存支持(基于 Caffeine),Ribbon 无内置缓存
- Spring Cloud 2020.0.x 起默认使用 LoadBalancer
Spring Cloud LoadBalancer 的工作流程?
① 请求到达
@LoadBalanced RestTemplate或WebClient;② 拦截器从请求 URL 中提取服务名;③LoadBalancerClientFactory根据服务名获取对应的ReactorLoadBalancer;④ServiceInstanceListSupplier从 DiscoveryClient 响应式获取实例列表;⑤ReactorLoadBalancer.choose()根据策略选择一个实例;⑥ 替换 URL 中的服务名为实际 IP:Port,发起真实请求。
如何在 LoadBalancer 中实现 Nacos 权重策略?
Nacos 实例元数据中包含
nacos.weight,可以自定义ReactorLoadBalancer读取该权重值,实现加权随机或加权轮询。Spring Cloud Alibaba 已内置 Nacos 权重负载均衡支持,引入spring-cloud-starter-alibaba-nacos-discovery后自动生效。
小结
Spring Cloud LoadBalancer 是 Ribbon 的官方继任者,基于响应式架构提供了更现代的负载均衡能力。迁移成本极低——RestTemplate + @LoadBalanced 注解完全兼容,自定义策略也只需实现 ReactorLoadBalancer 接口。名字找到了、实例选好了,下一步就是如何优雅地写调用代码——声明式 HTTP 客户端 Feign 登场。