LoadBalancer 和 Ribbon 的区别是什么?为什么用它替代 Ribbon?

#Java#面试八股

LoadBalancer 和 Ribbon 的区别是什么?为什么用它替代 Ribbon?

结论先行:Spring Cloud LoadBalancer 是 Spring Cloud 官方自研的客户端负载均衡器,用来替代进入维护模式的 Netflix Ribbon。核心差异是响应式与非阻塞:LoadBalancer 提供 ReactiveLoadBalancer(Reactor)与阻塞式 LoadBalancerClient 两套 API,可插拔策略、与 Spring 生态一致;Ribbon 是纯阻塞的、基于线程本地上下文的旧实现。

1. 为什么替换

时间线事件
2018Netflix 宣布 Ribbon 进入维护模式,不再新增功能
2020Spring Cloud Hoxton 起官方推荐 LoadBalancer
2021Spring Cloud 2020.0 移除 Ribbon 依赖
现在新项目默认只有 LoadBalancer,旧项目需手动降级或迁移

Ribbon 的维护停更只是导火索,真正的原因是它的设计与现代 Spring 栈不匹配:它依赖 ThreadLocal 的 RibbonLoadBalancerContext、基于阻塞的 IClientConfig,无法在 WebFlux 的非阻塞链路里工作。

2. 能力对比

维度RibbonSpring Cloud LoadBalancer
编程模型阻塞(RestTemplate 拦截器)响应式 ReactiveLoadBalancer + 阻塞 LoadBalancerClient
策略接口IRuleReactorServiceInstanceLoadBalancer
默认策略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 权重策略替换默认轮询。

笔记加载中…