第13课:Spring Cloud LoadBalancer 新版负载均衡核心原理

文章目录

适配版本 :Spring Cloud 2025.1.3(Oakwood)、Spring Boot 4.0.8、Spring Cloud Alibaba 2025.1.0.0、Nacos Server 3.1.1、JDK 21

课程定位:服务通信阶段开篇,从Ribbon淘汰讲到新版LoadBalancer架构,掌握负载均衡底层原理与算法实战

一、开篇:Ribbon的"退场"与新王的"登基"

第12课结束时,我们完成了配置中心阶段的全部内容。现在进入第四阶段:服务通信核心。这是微服务从"各自为政"走向"协作联动"的关键阶段。

在服务通信中,一个核心问题是:当服务A调用服务B时,服务B有多个实例,请求该发给谁?这就是负载均衡要解决的问题。

在Spring Cloud的早期版本中,这个问题的答案几乎只有一个:Netflix Ribbon。Ribbon是Netflix开源的客户端负载均衡组件,自2014年诞生以来就承担着Spring Cloud生态中客户端负载均衡的核心职责。它提供了丰富的功能:超时控制、重试机制、断路器模式、多种负载均衡策略(轮询、随机、可用性过滤等),在很长一段时间内是微服务负载均衡的"事实标准"。

但Ribbon的故事在2020年迎来了转折点。Spring Cloud 2020.0.0(Ilford)正式将Ribbon移除出核心依赖,取而代之的是Spring官方自研的Spring Cloud LoadBalancer。Ribbon的退役源于其与Netflix Eureka的紧密耦合------Eureka进入维护模式后,Ribbon也随之失去了支持,同时Ribbon不支持响应式编程,无法适配WebClient和Reactor技术栈。

Spring Cloud LoadBalancer(以下简称SCLB)作为替代者,从2017年就开始在Spring Cloud Incubator孵化器中开发,经过数年打磨最终成为官方标准。它不仅解决了Ribbon停更、不支持响应式编程的痛点,还采用了更轻量级的线程模型,在P99延迟指标上比Ribbon有15-20%的提升。

本课将从Ribbon淘汰原因讲到SCLB核心架构,从负载均衡算法源码讲到Nacos集成实战,从区域感知等高级特性讲到生产环境踩坑方案,完整覆盖新版负载均衡的所有关键点。

二、Ribbon淘汰原因深度剖析

2.1 三大退役原因

原因一:与Eureka的强绑定。Ribbon的核心依赖是Eureka提供的服务注册表信息。当Eureka进入维护模式后,Ribbon也失去了持续更新的基础。更关键的是,Ribbon的设计架构深度依赖Netflix的开源生态,无法独立演进。

原因二:不支持响应式编程。Spring Framework 5引入响应式编程模型后,WebClient、Reactor等技术栈成为微服务高并发场景的首选。Ribbon的线程模型是阻塞式的,无法适配响应式编程。Spring Cloud LoadBalancer天然适配WebClient和Reactor,支持反应式编程,满足高并发场景需求。

原因三:配置模型复杂 。Ribbon的配置模型较为复杂,大量参数需要调优(ribbon.ConnectTimeout、ribbon.ReadTimeout、ribbon.MaxAutoRetries等),且配置命名规则不够统一,给开发者带来了较高的学习成本。

2.2 替代关系对照

维度 Ribbon(已淘汰) Spring Cloud LoadBalancer
响应式支持 ❌ 不支持 ✅ 原生支持WebClient/Reactor
服务发现集成 Eureka绑定 通用DiscoveryClient抽象
默认策略 RoundRobinRule RoundRobinLoadBalancer
配置复杂度 高(参数众多) 低(简洁配置)
维护状态 已停止 官方活跃维护
扩展方式 ILoadBalancer接口 ReactorServiceInstanceLoadBalancer接口

核心结论 :新项目不应再使用Ribbon 。存量项目迁移到SCLB时,API层面的兼容性较好(@LoadBalanced注解仍然有效),主要变化在于配置项和自定义策略的实现方式。

三、LoadBalancer核心架构解析

3.1 核心组件架构

Spring Cloud LoadBalancer的架构设计非常清晰,由五个核心组件构成:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                     LoadBalancerClient                      │
│              客户端负载均衡统一入口                         │
│         choose() / execute() / reconstructURI()            │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│              LoadBalancerClientFactory                      │
│          为每个服务创建独立的LoadBalancer实例               │
│              基于 Spring 父子容器隔离                       │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│           ReactorServiceInstanceLoadBalancer                │
│                 负载均衡策略选择器                          │
│   RoundRobinLoadBalancer / RandomLoadBalancer / 自定义      │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│          ServiceInstanceListSupplier                        │
│                 服务实例列表供应器                          │
│   DiscoveryClientSupplier / Caching / HealthCheck / Zone   │
└──────────────────────────┬──────────────────────────────────┘
                           │
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                    DiscoveryClient                          │
│         从注册中心(Nacos/Consul/Eureka)获取实例列表       │
└─────────────────────────────────────────────────────────────┘

组件职责说明:

组件 职责 关键接口/类
LoadBalancerClient 负载均衡的统一入口,封装选择实例和执行请求的逻辑 BlockingLoadBalancerClient(阻塞式) / ReactiveLoadBalancer(响应式)
LoadBalancerClientFactory 为每个服务创建独立的LoadBalancer实例,基于Spring父子容器实现服务间的配置隔离 LoadBalancerClientFactory
ReactorServiceInstanceLoadBalancer 负载均衡策略的选择器,决定"选哪个实例" RoundRobinLoadBalancer / RandomLoadBalancer
ServiceInstanceListSupplier 服务实例列表的供应器,决定"有哪些实例可选" DiscoveryClientServiceInstanceListSupplier / CachingServiceInstanceListSupplier
DiscoveryClient 从注册中心获取实例列表的抽象层,支持Nacos、Consul等 ReactorDiscoveryClient / DiscoveryClient

3.2 核心流程详解

一次完整的负载均衡调用流程:

复制代码
Consumer 发起请求
    │
    ▼
LoadBalancerInterceptor 拦截请求(RestTemplate场景)
    │  从URL中提取服务名(如 "service-user")
    ▼
LoadBalancerClient.choose(serviceId)
    │
    ▼
LoadBalancerClientFactory 获取该服务的LoadBalancer实例
    │
    ▼
ServiceInstanceListSupplier.get() 获取实例列表
    │  从本地缓存获取(缓存为空时从Nacos拉取)
    │  应用HealthCheck过滤掉不健康实例
    │  应用ZonePreference过滤跨区域实例
    ▼
ReactorServiceInstanceLoadBalancer.choose(request)
    │  应用负载均衡算法(轮询/随机/自定义)
    ▼
返回选定的 ServiceInstance
    │  ServiceInstance 包含 ip、port、metadata 等信息
    ▼
reconstructURI() 将服务名替换为 IP:port
    │  http://service-user/api/user → http://192.168.1.100:8081/api/user
    ▼
发起真实HTTP请求

关键理解 :ServiceInstanceListSupplier和ReactorServiceInstanceLoadBalancer的分工是------前者负责"有哪些实例可选"(过滤后的候选列表),后者负责"从候选中选哪个"(算法决策)。

3.3 核心接口源码解析

ReactorServiceInstanceLoadBalancer接口------负载均衡算法的实现入口:

java 复制代码
public interface ReactorServiceInstanceLoadBalancer extends ReactorLoadBalancer<ServiceInstance> {

    Mono<Response<ServiceInstance>> choose(Request request);
}

RoundRobinLoadBalancer源码核心逻辑:

java 复制代码
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {

    private final AtomicInteger position;  // 原子计数器

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
                .getIfAvailable(NoopServiceInstanceListSupplier::new);
        return supplier.get(request).next()
                .map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
    }

    private Response<ServiceInstance> processInstanceResponse(
            ServiceInstanceListSupplier supplier, List<ServiceInstance> serviceInstances) {
        Response<ServiceInstance> response = getInstanceResponse(serviceInstances);
        if (supplier instanceof SelectedInstanceCallback && response.hasServer()) {
            ((SelectedInstanceCallback) supplier).selectedServiceInstance(response.getServer());
        }
        return response;
    }

    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        if (instances.isEmpty()) {
            return new EmptyResponse();
        }
        int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
        ServiceInstance instance = instances.get(pos % instances.size());
        return new DefaultResponse(instance);
    }
}

核心逻辑解读:

  • position.incrementAndGet() & Integer.MAX_VALUE:原子递增计数器,& Integer.MAX_VALUE确保结果始终为正数(防止溢出为负数)
  • pos % instances.size():取模运算,确保索引在实例列表范围内循环
  • 每次调用递增计数器,实现"轮流选择"的效果

ServiceInstanceListSupplier接口------实例列表的供应入口:

java 复制代码
public interface ServiceInstanceListSupplier extends Supplier<Flux<List<ServiceInstance>>> {

    String getServiceId();

    default Flux<List<ServiceInstance>> get() {
        return get(null);
    }

    default Flux<List<ServiceInstance>> get(Request request) {
        return get();
    }
}

SCLB提供了多种ServiceInstanceListSupplier实现,通过Builder模式组合:

Supplier类型 功能 是否必需
DiscoveryClientServiceInstanceListSupplier 从DiscoveryClient获取实例列表 ✅ 必需
CachingServiceInstanceListSupplier 缓存实例列表(默认TTL 35秒) 推荐
HealthCheckServiceInstanceListSupplier 主动健康检查,过滤不健康实例 推荐
ZonePreferenceServiceInstanceListSupplier 区域感知,优先选择同区域实例 按需

Builder模式的典型用法:

java 复制代码
public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                .withDiscoveryClient()
                .withCaching()
                .withHealthChecks()
                .build(context);
    }
}

生产建议 :官方文档明确指出,虽然基础的非缓存实现 对原型设计和测试有用,但效率远低于缓存版本,因此生产环境推荐始终使用缓存版本。

四、负载均衡算法深度剖析

4.1 默认策略:轮询(RoundRobin)

SCLB的默认策略是轮询 ,按照实例列表的顺序依次选择,到达末尾后重新开始。轮询策略适用于所有实例性能相近的场景,保证每个实例获得均等的流量分配。

算法特点:

  • 实现简单,无状态依赖
  • 天然公平,每个实例获得等量请求
  • 不感知实例的负载情况

源码实现 :如第3.3节所示,通过AtomicInteger计数器实现。

4.2 随机策略(Random)

随机策略从可用实例列表中完全随机 选择一个实例。适用于实例性能相近且需要避免热点集中的场景。

为什么默认策略不是随机? 轮询比随机更可预测,在实例数量少时可以保证严格均衡。随机的优势在于当实例数量多时,随机分布更均匀,且避免了轮询在实例数量变化时可能出现的"突变"。

配置方式:

java 复制代码
public class CustomLoadBalancerConfiguration {

    @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);
    }
}

4.3 加权策略(Weighted)

当服务实例的性能不一致时(如不同规格的机器),轮询和随机策略都无法实现按性能比例分配流量。SCLB提供了加权负载均衡支持。

Nacos权重集成 :在Spring Cloud Alibaba环境中,Nacos实例的weight字段是元数据属性。但Nacos本身不强制流量分配------客户端框架读取权重并在负载均衡时应用。

关键问题 :默认情况下,Spring Cloud Alibaba将Nacos权重视为二值(零权重表示不接收流量,非零权重表示均等流量)。要启用真正的加权负载均衡,需要激活Nacos感知的负载均衡器:

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      nacos:
        enabled: true

或者直接启用SCLB的加权Supplier:

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      configurations: weighted

也可以通过编程方式配置:

java 复制代码
public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                .withDiscoveryClient()
                .withCaching()
                .withWeighted()
                .build(context);
    }
}

4.4 区域感知策略(Zone Preference)

区域感知是分布式部署中减少跨区域网络延迟的关键能力。当服务实例分布在多个可用区(Zone)或数据中心时,客户端应优先选择同区域的实例,仅在本地区域无可用实例时才跨区域调用。

配置方式:

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      configurations: zone-preference

或编程方式:

java 复制代码
public class CustomLoadBalancerConfiguration {

    @Bean
    public ServiceInstanceListSupplier discoveryClientServiceInstanceListSupplier(
            ConfigurableApplicationContext context) {
        return ServiceInstanceListSupplier.builder()
                .withDiscoveryClient()
                .withCaching()
                .withZonePreference()
                .build(context);
    }
}

区域信息传递 :区域信息通过实例元数据中的zone字段传递。在Nacos中,可以通过实例元数据设置:

yaml 复制代码
spring:
  cloud:
    nacos:
      discovery:
        metadata:
          zone: cn-shenzhen-a

4.5 同实例优先策略(Same Instance Preference)

该策略会优先选择上一次选中的实例,如果该实例仍然可用。适用于需要保持会话粘性的场景。这也是Zookeeper StickyRule的替代方案。

配置方式:

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      configurations: same-instance-preference

4.6 策略组合与优先级

多个Supplier可以叠加使用,执行顺序由Builder的调用顺序决定:

java 复制代码
ServiceInstanceListSupplier.builder()
        .withDiscoveryClient()      // 1. 从注册中心获取实例
        .withHealthChecks()          // 2. 过滤不健康实例
        .withZonePreference()        // 3. 区域优先过滤
        .withCaching()               // 4. 缓存最终结果
        .build(context);

执行顺序即为过滤链的顺序 。withHealthChecks()放在withZonePreference()之前,意味着先过滤不健康实例,再做区域优先选择。

五、Nacos集成实战

5.1 依赖配置

在服务消费者模块(如service-order)的POM中添加:

xml 复制代码
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

注意 :在Spring Cloud 2025.1.x中,如果使用了spring-cloud-starter-alibaba-nacos-discovery,LoadBalancer依赖会自动传递引入。但显式声明仍然推荐,以确保版本可控。

5.2 启用LoadBalancer

在配置类中添加@LoadBalanced注解:

java 复制代码
@Configuration
public class RestTemplateConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@LoadBalanced注解给RestTemplate添加了一个拦截器LoadBalancerInterceptor,当请求URL中的主机名是服务名时,拦截器会调用LoadBalancer选择实例,然后将服务名替换为实际的IP:端口。

5.3 验证负载均衡

启动两个service-user实例(8081和8083),通过service-order多次调用用户接口,观察两个实例的请求分布。默认的轮询策略会交替选择两个实例。

5.4 Nacos感知的权重负载均衡

如果需要Nacos权重生效,在service-order的application.yml中添加:

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      nacos:
        enabled: true
      configurations: weighted

然后在Nacos控制台修改实例权重,观察流量按权重分配。

六、2025版新特性

Spring Cloud 2025.1.0(Oakwood)为LoadBalancer带来了多项增强:

特性一:LoadBalancer API版本控制支持。Spring Cloud Commons添加了LoadBalancer API的版本控制支持,可以根据请求头或参数中的API版本信息,选择对应版本的实例。这为灰度发布和API版本管理提供了底层能力。

特性二:JSpecify空安全注解。所有公共API类使用JSpecify进行空安全性注解,IDE可以更准确地提示潜在的NullPointerException,减少运行时崩溃。

特性三:Spring Interface Clients集成 。Spring Interface Clients AutoConfiguration集成了负载均衡器,通过@HttpExchange声明式客户端可以直接使用LoadBalancer进行服务调用。

七、生产环境踩坑指南

坑一:滚动发布期间调用失败------缓存是"真凶"

现象 :滚动发布时,大量调用失败,报Connection refused或Timeout。

原因 :SCLB默认的CachingServiceInstanceListSupplier缓存TTL为35秒。在这35秒内,即使Nacos已经推送了实例变更,SCLB仍然使用缓存的旧实例列表,导致请求被路由到已下线的实例。

解决方案:在Nacos 2.x/3.x环境中,关闭SCLB缓存或缩短缓存TTL:

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      cache:
        enabled: false    # 关闭缓存

或调整缓存TTL:

yaml 复制代码
spring:
  cloud:
    loadbalancer:
      cache:
        ttl: 5s    # 缩短至5秒

更优方案 :自定义ServiceInstanceListSupplier,直接监听Nacos的实例变更事件,实现实时刷新。

坑二:Nacos权重修改后不生效

现象:在Nacos控制台修改权重后,流量分布没有变化。

原因 :Spring Cloud Alibaba默认将Nacos权重视为二值(零权重不接收流量,非零权重均等流量),不启用真正的加权路由。

解决 :配置spring.cloud.loadbalancer.nacos.enabled=true和spring.cloud.loadbalancer.configurations=weighted。

坑三:LoadBalancer与Ribbon同时存在classpath

现象:启动时报类冲突或负载均衡行为异常。

原因:项目中同时存在Ribbon和LoadBalancer的依赖。

解决:排除Ribbon依赖,确保classpath中只有LoadBalancer。

坑四:区域感知配置后仍然跨区域调用

现象 :配置了zone-preference后,请求仍然路由到其他区域的实例。

原因 :实例元数据中未设置zone字段,或所有实例在同一个Zone中。

解决 :在Nacos实例元数据中设置zone字段,并确保实例分布在不同区域。

坑五:自定义策略不生效

现象 :自定义的ReactorServiceInstanceLoadBalancer Bean未生效。

原因 :自定义配置类被@ComponentScan扫描到,导致全局生效(影响了所有服务)。

解决 :自定义配置类不应放在@ComponentScan扫描路径下,应通过@LoadBalancerClient或@LoadBalancerClients注解指定为特定服务生效。

八、课后作业

作业一 :在service-order中确认已引入spring-cloud-starter-loadbalancer依赖,启动两个service-user实例(8081和8083),通过@LoadBalanced RestTemplate多次调用,观察轮询效果。

作业二 :将service-order的负载均衡策略切换为随机策略,配置自定义RandomLoadBalancer,验证两个实例的请求分布变为随机。

作业三 :配置spring.cloud.loadbalancer.nacos.enabled=true,在Nacos控制台将8083的权重设为10,验证流量按权重分配(约91%流量到8081)。

作业四(进阶) :实现一个自定义的ReactorServiceInstanceLoadBalancer,基于实例的metadata.version字段实现版本感知的负载均衡(优先选择指定版本的实例)。通过@LoadBalancerClient注解将其应用于service-user。

九、下节预告

第14课将进入OpenFeign声明式远程调用完整实战。我们将从Feign核心原理讲到接口式调用的完整实战,涵盖参数传递、文件上传下载、请求头传递、超时配置等高频使用场景。LoadBalancer作为底层负载均衡引擎,将在OpenFeign的调用链中自动生效。理解本课的LoadBalancer原理后,第14课的Feign调用将更加"知其所以然"------为什么Feign能通过服务名调用、为什么默认是轮询策略、如何自定义负载均衡行为,这些问题都将在第14课中得到实战验证。

🔗《最新版 SpringCloud 2025 从入门到实战》系列课程导航

去订阅

第一部分:微服务前置基础 & 新版环境搭建(第1-5课)

第二部分:注册中心核心(Nacos 最新版)(第6-9课)

第三部分:配置中心核心(Nacos配置中心)(第10-12课)

第四部分:服务通信核心(OpenFeign + LoadBalancer)(第13-16课)

第五部分:网关核心(SpringCloud Gateway 新版)(第17-20课)

第六部分:熔断、限流、降级(Sentinel 新版)(第21-24课)

第七部分:微服务监控、链路追踪、日志体系(第25-28课)

第八部分:微服务高阶特性 & 分布式核心能力(第29-31课)

第九部分:企业级完整项目实战 & 架构复盘(第32-35课)

相关推荐
code_slave(码畜)1 小时前
微服务架构落地:基础服务 —— 报表服务(上篇:定位、边界与整体架构)
java·spring boot·spring cloud·微服务·架构
苦瓜不会码代码1 小时前
负载均衡会话保持-扩容后登录掉线踩坑
运维·负载均衡
Thomas.Sir15 小时前
第12课:Nacos 配置加密、版本回溯、配置监听 & 生产风控方案
spring cloud·nacos
贾斯汀frank1 天前
Spring Boot + K8s + Istio 与 Spring Cloud:微服务治理能力对比
spring cloud·微服务·istio
Thomas.Sir2 天前
第11课:Nacos 多环境、多服务配置隔离 & 配置优先级详解
spring cloud·微服务·nacos
涵美女程序猿3 天前
ChatCut online 剪负载均衡健康检查演练录屏:后端池、探针路径与摘除阈值怎样对应
运维·程序人生·电脑·负载均衡
Thomas.Sir4 天前
第8课:Nacos健康检测、权重配置、环境隔离实战
spring cloud·微服务
SL_staff4 天前
JVS-Rules vs Drools:金融风控团队为何转向业务可编辑的决策平台
java·spring cloud·github
阿狗童鞋4 天前
Nginx反向代理与负载均衡实战指南
运维·nginx·负载均衡