LoadBalancer 和 Ribbon 的区别是什么?为什么用它替代 Ribbon?
结论先行:Spring Cloud LoadBalancer 是 Spring Cloud 官方自研的客户端负载均衡器,用来替代进入维护模式的 Netflix Ribbon。核心差异是响应式与非阻塞:LoadBalancer 提供 ReactiveLoadBalancer(Reactor)与阻塞式 LoadBalancerClient 两套 API,可插拔策略、与 Spring 生态一致;Ribbon 是纯阻塞的、基于线程本地上下文的旧实现。
1. 为什么替换
| 时间线 | 事件 |
|---|---|
| 2018 | Netflix 宣布 Ribbon 进入维护模式,不再新增功能 |
| 2020 | Spring Cloud Hoxton 起官方推荐 LoadBalancer |
| 2021 | Spring Cloud 2020.0 移除 Ribbon 依赖 |
| 现在 | 新项目默认只有 LoadBalancer,旧项目需手动降级或迁移 |
Ribbon 的维护停更只是导火索,真正的原因是它的设计与现代 Spring 栈不匹配:它依赖 ThreadLocal 的 RibbonLoadBalancerContext、基于阻塞的 IClientConfig,无法在 WebFlux 的非阻塞链路里工作。
2. 能力对比
| 维度 | Ribbon | Spring Cloud LoadBalancer |
|---|---|---|
| 编程模型 | 阻塞(RestTemplate 拦截器) | 响应式 ReactiveLoadBalancer + 阻塞 LoadBalancerClient |
| 策略接口 | IRule | ReactorServiceInstanceLoadBalancer |
| 默认策略 | ZoneAvoidanceRule(轮询 + 区域规避) | RoundRobinLoadBalancer(轮询) |
| 实例来源 | ServerList(DiscoveryEnabledNIWSServerList) | ServiceInstanceListSupplier(可组合) |
| 缓存 | ServerListRefreshExecutor(默认 30s) | LoadBalancerCacheManager(默认 35s) |
| 健康检查 | 自建 ping | 可由 HealthCheckServiceInstanceListSupplier 组合 |
| 自定义方式 | 配置类 + @RibbonClient | @LoadBalancerClient / @LoadBalancerClients |
| WebFlux 支持 | 不支持 | 原生支持 |
3. 使用方式
@Configuration
public class RestClientConfig {
@Bean
@LoadBalanced // 关键:让 RestTemplate 走服务发现
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(1))
.setReadTimeout(Duration.ofSeconds(2))
.build();
}
}
// 调用时用服务名代替 IP
String url = "http://stock-service/stock/" + skuId;
StockDTO dto = restTemplate.getForObject(url, StockDTO.class);
自定义负载均衡策略,注意不能加 @Configuration,否则会变成全局生效:
public class SameClusterFirstLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ServiceInstanceListSupplier supplier;
private final AtomicInteger position = new AtomicInteger(0);
public SameClusterFirstLoadBalancer(ServiceInstanceListSupplier supplier) {
this.supplier = supplier;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get().next().map(instances -> {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 优先同集群,再轮询
String zone = ZoneContext.current();
List<ServiceInstance> sameZone = instances.stream()
.filter(i -> zone.equals(i.getMetadata().get("zone")))
.toList();
List<ServiceInstance> candidates = sameZone.isEmpty() ? instances : sameZone;
int idx = Math.abs(position.getAndIncrement() % candidates.size());
return new DefaultResponse(candidates.get(idx));
});
}
}
@LoadBalancerClient(name = "stock-service", configuration = SameClusterFirstLoadBalancer.class)
4. 缓存与「实例列表延迟」
LoadBalancer 会把 ServiceInstanceListSupplier 的结果缓存 35 秒(spring.cloud.loadbalancer.cache.ttl)。这意味着注册中心里实例下线后,调用方最多还会往旧实例发 35 秒请求。Ribbon 时代这个值是 30 秒,是同一类问题的不同默认值。
spring:
cloud:
loadbalancer:
cache:
enabled: true
ttl: 10s # 缩短缓存,故障切换更快;代价是更频繁地拉注册中心
health-check:
initial-delay: 10s
interval: 15s
5. 常见追问
追问一:LoadBalancer 如何与 Nacos 权重配合?
默认轮询不读权重。要用权重需换成 NacosLoadBalancer(Spring Cloud Alibaba 提供),它按实例的 weight 做加权随机,并支持同集群优先。
追问二:@LoadBalanced 的原理是什么?
它给 RestTemplate 加一个 LoadBalancerInterceptor,拦截 http://service-name/... 形式的请求,解析服务名后选实例、替换成真实地址再发请求。WebClient 则用 ReactorLoadBalancerExchangeFilterFunction。
追问三:为什么自定义配置类不能加 @Configuration?
加了会被父容器扫描成全局配置,导致所有服务的负载均衡策略都被改掉。正确做法是放在不被组件扫描的包下,通过 @LoadBalancerClient(configuration = ...) 精确绑定。
追问四:LoadBalancer 支持重试吗?
支持,配合 spring-retry:spring.cloud.loadbalancer.retry.enabled=true,并注意只对幂等请求开启,避免重复下单。
追问五:实例全部下线时会怎样?
choose 返回空实例,抛出 IllegalStateException: No instances available for xxx。生产上要配合熔断降级兜底,而不是让异常直接打到用户。
小结
LoadBalancer 替代 Ribbon,本质是从阻塞式、ThreadLocal 绑定的旧模型,迁移到响应式、可组合的 ServiceInstanceListSupplier + ReactorServiceInstanceLoadBalancer 模型。迁移时要重点检查三件事:策略配置的作用域、缓存 TTL 带来的切换延迟、以及是否需要用 Nacos 权重策略替换默认轮询。