Spring Cloud LoadBalancer 最佳实践

发布于:2025-08-19 ⋅ 阅读:(18) ⋅ 点赞:(0)

Ribbon 曾经是 Spring Cloud 家族默认的客户端负载均衡工具,而 Spring Cloud LoadBalancer (SCLB) 是官方替换 Ribbon 的新实现。表面上它们都解决 “服务调用时选哪个实例” 的问题,但在理念、架构和生态上差异不小。


一、Ribbon  vs  SCLB


1. 定位和生态地位

  • Ribbon

    • Netflix OSS 出品,老一代的客户端负载均衡器。

    • 在 Spring Cloud Dalston ~ Greenwich 时代是默认选择。

    • 后来 Netflix OSS 宣布 进入维护模式(2018年起不再活跃发展)

  • Spring Cloud LoadBalancer

    • Spring 团队自研,替代 Ribbon。

    • 完全独立于 Netflix 生态,不再依赖过时组件。

    • 与 Spring Boot 2.x/3.x、Reactor、WebClient、Feign 等深度集成。


2. 核心设计思路

  • Ribbon

    • 侵入性较强:依赖 IClientConfigIRuleIPing 等接口,配置体系复杂。

    • 强调“策略类 + 配置类”模式(比如 RoundRobinRule、ZoneAvoidanceRule)。

    • 同步调用模型为主,虽然可扩展但偏“重量级”。

    • RestTemplate 深度耦合(通过 @LoadBalanced RestTemplate)。

  • Spring Cloud LoadBalancer

    • 函数式 + 轻量化:核心是 ServiceInstanceListSupplier(负责提供实例列表)和 ReactorServiceInstanceLoadBalancer(负责挑选实例)。

    • 完全 Reactor 化,支持响应式编程(Reactor/Flux/Mono),天然适配 WebClient。

    • API 更简单,默认策略是 RoundRobin,但很容易定制。

    • 更解耦,和 DiscoveryClient、Feign、gRPC 等可自由组合。


3. 扩展能力

  • Ribbon

    • 有比较多的现成策略:RoundRobinRuleRandomRuleRetryRuleWeightedResponseTimeRule

    • 自定义需要继承 IRule,配置也要绕 Ribbon 的专用配置体系。

    • 支持 Zone 概念(跨机房/多可用区),适合 Netflix 内部环境,但在普通企业里很少用上。

  • Spring Cloud LoadBalancer

    • 策略很“干净”:只要实现 ReactorServiceInstanceLoadBalancer 接口即可。

    • ServiceInstanceListSupplier 提供了天然的 hook:你可以在实例列表进入负载均衡前加上 过滤、排序、权重

    • 没有 Ribbon 那种内置十几个策略的复杂度,但用组合的方式,灵活度更高。


4. 与 Spring 生态的关系

  • Ribbon

    • 被强绑定到 RestTemplate + Feign(老版本)。

    • Spring Cloud Netflix 维护成本高,升级阻力大。

  • Spring Cloud LoadBalancer

    • 未来路线核心组件,和 Spring Cloud Gateway、Feign 3.x、WebClient 全兼容。

    • 官方推荐替代方案,Ribbon 已经标记 Deprecated

    • 随 Spring Boot/Spring Cloud 版本更新,能持续获得支持。


5. 性能与现代化

  • Ribbon

    • 基于老旧同步模型(虽然功能全,但显得笨重)。

    • 没有天然的 Reactor 支持,在响应式场景里不合拍。

  • Spring Cloud LoadBalancer

    • 基于 Reactor,轻量、非阻塞,天然适合高并发场景。

    • 使用 Flux<ServiceInstance>,可以灵活叠加缓存、权重、健康检查逻辑。

    • 更加云原生,能和 Kubernetes Service、Consul、Nacos 等平滑对接。


二、源码说明

1. 核心抽象

  • Ribbon

    • 结果:实例获取、缓存、负载均衡策略,全都绑死在 Ribbon 的体系

    • 负载均衡器接口:ILoadBalancer
      内部维护实例列表(ServerList),并交给 IRule 挑选。

    • 负载均衡策略接口:IRule

      public interface IRule {
          Server choose(Object key);
          void setLoadBalancer(ILoadBalancer lb);
          ILoadBalancer getLoadBalancer();
      }

      代表“从一堆服务实例里挑一个”。

    • Spring Cloud LoadBalancer (SCLB)

      • 实例供应接口:

        public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {
            String getServiceId();
        }

        负责“提供候选实例列表”,来源可以是 DiscoveryClient、缓存、静态配置。

      • 策略接口:

        public interface ReactorServiceInstanceLoadBalancer {
            Mono<Response<ServiceInstance>> choose(Request request);
        }

        专注于“如何从候选列表里挑一个”。

      • 结果:候选列表与选择逻辑完全解耦,职责单一,而且基于 Reactor(非阻塞)。


2. Spring Cloud LoadBalancer 和 Feign 相对 Ribbon 的解耦性

看源码上的调用链对比最明显:

  • Ribbon + Feign
    Feign 的 LoadBalancerFeignClient → 直接调用 RibbonLoadBalancerClient → 使用 ILoadBalancer + IRule 绑定。

    public class RibbonLoadBalancerClient implements LoadBalancerClient {
        @Override
        public ServiceInstance choose(String serviceId) {
            ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
            Server server = loadBalancer.chooseServer(null);
            return new RibbonServer(serviceId, server, isSecure(server), serverIntrospector(serviceId).getMetadata(server));
        }
    }

    可以看到:Feign 和 Ribbon 耦合紧密ILoadBalancerIRule 必须都存在。

  • SCLB + Feign(Spring Cloud 2020+)
    Feign 的 LoadBalancerFeignClient → 使用 BlockingLoadBalancerClientReactorLoadBalancerExchangeFilterFunction

    public class BlockingLoadBalancerClient implements LoadBalancerClient {
        private final LoadBalancerClientFactory clientFactory;
        @Override
        public ServiceInstance choose(String serviceId) {
            ReactorServiceInstanceLoadBalancer loadBalancer = 
                clientFactory.getInstance(serviceId, ReactorServiceInstanceLoadBalancer.class);
            // 核心是调用 loadBalancer.choose()
        }
    }

    这里的关键:

    • Feign 只依赖于 ReactorServiceInstanceLoadBalancer 抽象,而不关心实例供应如何实现。

    • ServiceInstanceListSupplier 可随时替换(比如 Kubernetes、Consul、Nacos),Feign 本身无需改动。

👉 结论:Ribbon 时代 Feign 直接依赖 Ribbon 核心接口,导致强绑定;SCLB 下 Feign 只依赖于统一的 LoadBalancer 抽象,而实例来源和策略完全可插拔 → 解耦性更高


三、Spring Cloud LoadBalancer 最佳实践

1. 基础使用

引入依赖

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

RestTemplate / WebClient 集成

@Bean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder.build();
}

@Bean
@LoadBalanced
public WebClient.Builder webClientBuilder() {
    return WebClient.builder();
}

建议优先用 WebClient,因为它能发挥 SCLB 的 Reactor 非阻塞优势。


2. 缓存与性能优化

SCLB 默认每次请求会调用 DiscoveryClient 获取实例,可以用内置的 缓存供应器

@Bean
public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
        ConfigurableApplicationContext context) {
    return ServiceInstanceListSupplier.builder()
        .withDiscoveryClient()
        .withCaching() // 启用缓存
        .build(context);
}

这样能减少注册中心压力,尤其是在高并发场景。


3. 自定义策略

简单示例:基于元数据的优先级选择

比如实例 metadata 里有 "zone": "shanghai",只要优先调用同城实例:

@Bean
ReactorServiceInstanceLoadBalancer zoneAwareLoadBalancer(
        ObjectProvider<ServiceInstanceListSupplier> supplierProvider) {
    return new ReactorServiceInstanceLoadBalancer() {
        @Override
        public Mono<Response<ServiceInstance>> choose(Request request) {
            return supplierProvider.getIfAvailable().get()
                .next()
                .map(instances -> {
                    List<ServiceInstance> localZone = instances.stream()
                        .filter(i -> "shanghai".equals(i.getMetadata().get("zone")))
                        .toList();
                    if (!localZone.isEmpty()) {
                        return new DefaultResponse(localZone.get(0));
                    }
                    return new DefaultResponse(instances.get(0));
                });
        }
    };
}

更复杂:权重路由

实现 ReactorServiceInstanceLoadBalancer,结合 metadata.weight 字段,做加权随机选择。这相当于 Ribbon 的 WeightedResponseTimeRule,但在 SCLB 里更灵活。


4. ServiceInstanceListSupplier 增强

SCLB 提供了“供应链”思想,常用扩展点:

  • 过滤:过滤掉 metadata 标记为 "status=down" 的实例;

  • 排序:按响应时间/CPU负载排序,把健康的放前面;

  • 包装:组合多层供应器(比如先 DiscoveryClient → 再缓存 → 再 zone 过滤)。

这种链式增强比 Ribbon 的配置类清晰得多。


5. 与 Feign 配合

在新版 Spring Cloud 中,Feign 默认走 SCLB。最佳实践:

  • 配置 spring.cloud.loadbalancer.retry.enabled=true → 自动开启重试机制;

  • 自定义 ReactorServiceInstanceLoadBalancer,Feign 会自动走你的策略;

  • 如果想做更细粒度控制,可以写 @FeignClient(configuration=...) 指定独立的 LoadBalancer 配置。


6. 容错与重试

SCLB 不再像 Ribbon 那样内置 RetryRule,而是把重试交给 Spring Retry

spring:
  cloud:
    loadbalancer:
      retry:
        enabled: true
        retry-on-all-operations: true
        max-retries-on-same-service-instance: 1
        max-retries-on-next-service-instance: 2

这样能保证单实例失败 → 自动切换下一个实例,避免单点问题。


7. 云原生环境最佳实践

  • Kubernetes 上,推荐直接基于 spring-cloud-starter-kubernetes-client + SCLB,实例供应就是 Pod 列表;

  • Nacos / Consul 场景下,直接使用对应 DiscoveryClient starter,SCLB 自动适配;

  • 保持 ServiceInstance metadata 丰富(zone、weight、tag),方便做策略。


8. 迁移建议(Ribbon → SCLB)

  • RestTemplate 上的 @LoadBalanced 不需要动,底层自动切换为 SCLB;

  • Feign 默认走 SCLB,不需要额外改动;

  • Ribbon 自定义的 IRule 策略,需要改写成 ReactorServiceInstanceLoadBalancer

  • Ribbon 的 Zone/Weight 逻辑,迁移到 ServiceInstanceListSupplier + metadata 策略更清晰。


总结

最佳实践关键就是 “解耦 + 插拔”

  • 实例来源 → DiscoveryClient + 缓存

  • 实例增强 → Supplier 过滤/排序/权重

  • 策略 → 自定义 LoadBalancer

  • 调用 → WebClient / Feign

  • 容错 → Spring Retry


网站公告

今日签到

点亮在社区的每一天
去签到