Spring Cloud LoadBalancer 详解:客户端负载均衡的原理与实践

Spring Cloud LoadBalancer 详解:客户端负载均衡的原理与实践

  • 一、负载均衡概述
  • [二、Spring Cloud LoadBalancer 简介](#二、Spring Cloud LoadBalancer 简介)
    • [⭐ LoadBalancer vs Ribbon(面试高频)](#⭐ LoadBalancer vs Ribbon(面试高频))
  • 三、工作原理:从源码视角看一次调用
    • [3.1 核心组件(增强版)](#3.1 核心组件(增强版))
    • [⭐ 为什么要这样分层(非常重要)](#⭐ 为什么要这样分层(非常重要))
    • [3.2 执行流程](#3.2 执行流程)
    • [⭐ 实例列表是否每次都从注册中心获取?](#⭐ 实例列表是否每次都从注册中心获取?)
    • [3.3 Reactive 模式的本质区别](#3.3 Reactive 模式的本质区别)
  • [四、在项目中使用 Spring Cloud LoadBalancer](#四、在项目中使用 Spring Cloud LoadBalancer)
      • [4.1 添加依赖](#4.1 添加依赖)
      • [4.2 启用负载均衡的 RestTemplate](#4.2 启用负载均衡的 RestTemplate)
      • [4.3 使用 WebClient(反应式)](#4.3 使用 WebClient(反应式))
      • [4.4 负载均衡算法配置](#4.4 负载均衡算法配置)
  • 五、负载均衡算法
      • [5.1 开启服务发现](#5.1 开启服务发现)
      • [5.2 同集群优先访问](#5.2 同集群优先访问)
    • [⭐ 关于"权重"的重要说明(容易踩坑)](#⭐ 关于“权重”的重要说明(容易踩坑))
  • [六、负载均衡 + 重试机制(关键补充)](#六、负载均衡 + 重试机制(关键补充))
  • 七、与注册中心集成(增强说明)
    • [⭐ 架构关系图(建议记住)](#⭐ 架构关系图(建议记住))
  • 八、生产环境常见问题(重点加分)
    • [1️⃣ 实例雪崩(热点实例)](#1️⃣ 实例雪崩(热点实例))
    • [2️⃣ 脏实例问题](#2️⃣ 脏实例问题)
    • [3️⃣ 跨机房调用](#3️⃣ 跨机房调用)
    • [4️⃣ 冷启动问题](#4️⃣ 冷启动问题)
  • 九、常见问题(补充增强)
    • [9.1 LoadBalancer 不生效](#9.1 LoadBalancer 不生效)
    • [9.2 实例为空](#9.2 实例为空)
    • [9.3 自定义负载均衡注意点(补充)](#9.3 自定义负载均衡注意点(补充))
  • 十、总结
      • [1️⃣ 解耦设计](#1️⃣ 解耦设计)
      • [2️⃣ 响应式支持](#2️⃣ 响应式支持)
      • [3️⃣ 高扩展性](#3️⃣ 高扩展性)

在微服务架构中,一个服务通常会部署多个实例来实现高可用和水平扩展。当服务消费者需要调用这些实例时,如何选择其中一个进行请求?这便是负载均衡 要解决的问题。本文将深入介绍 Spring Cloud 官方推荐的客户端负载均衡器------Spring Cloud LoadBalancer,并解析其工作原理、设计思想、使用方法以及与 Nacos 等注册中心的集成。


一、负载均衡概述

负载均衡分为两类:

  • 服务端负载均衡:通过独立的负载均衡器(如 Nginx、F5)将请求分发到后端服务器。

  • 客户端负载均衡:服务消费者自己维护服务实例列表,并通过内置的负载均衡算法选择一个实例发起调用。Spring Cloud LoadBalancer 属于客户端负载均衡。

为什么越来越多系统选择客户端负载均衡?

  • 去中心化,避免单点瓶颈

  • 减少一次网络跳转(性能更高)

  • 更灵活(可以按调用方定制策略)


二、Spring Cloud LoadBalancer 简介

Spring Cloud LoadBalancer 是 Spring Cloud 官方提供的客户端负载均衡器,用于替代已进入维护状态的 Netflix Ribbon。它轻量、易于扩展,并且与 Spring Cloud 生态无缝集成,支持两种工作模式:

  • 反应式(Reactive):配合 WebClient 使用

  • 阻塞式(Blocking):配合 RestTemplate 使用


⭐ LoadBalancer vs Ribbon(面试高频)

维度 Ribbon LoadBalancer
状态 停止维护 官方推荐
模型 阻塞 支持响应式
架构 紧耦合 解耦设计
扩展方式 Rule 类 Supplier + Reactor

👉 本质区别:

LoadBalancer 是"组件解耦 + 响应式重构"的新一代实现


三、工作原理:从源码视角看一次调用

我们以 lqb-user 调用 lqb-bank 为例。


3.1 核心组件(增强版)

  • LoadBalancerInterceptor :拦截请求,实现 ClientHttpRequestInterceptor,负责拦截经过 RestTemplate 的 HTTP 请求。

  • LoadBalancerClient:统一调用入口,负载均衡客户端,核心接口,负责根据服务名选择实例。

  • ServiceInstanceListSupplier:提供实例列表

  • ReactorLoadBalancer:负载均衡算法,具体负载均衡算法实现,如轮询、随机等。


⭐ 为什么要这样分层(非常重要)

text 复制代码
职责拆分如下:

- Interceptor:解耦 HTTP 客户端
- Client:统一入口
- Supplier:解耦服务发现(可接 Nacos/Eureka/缓存)
- LoadBalancer:专注算法

👉 本质:

高内聚 + 低耦合 + SPI 可扩展设计


3.2 执行流程

  • 核心流程:

    RestTemplate

    LoadBalancerInterceptor

    LoadBalancerClient

    ServiceInstanceListSupplier(可能带缓存)

    ReactorLoadBalancer(选择实例)

    真实 HTTP 请求

  • LoadBalancerInterceptor 做了什么? 它会拦截 HTTP 请求,从 URL 中提取服务名(host),然后调用 LoadBalancerClient 来选择具体实例,并替换请求地址。
  • 服务实例是怎么拿到的? 通过 ServiceInstanceListSupplier 获取,通常是从注册中心(如 Nacos、Eureka)拉取。
  • 实际上中间通常会有缓存层,例如 CachingServiceInstanceListSupplier,避免每次都访问注册中心。

  • 概略:

    用户请求 → RestTemplate → LoadBalancerInterceptor

    LoadBalancerInterceptor#intercept()

    解析 URL,提取服务名(例如 lqb-bank)

    调用 LoadBalancerClient#execute() 或 #choose()

    通过 ServiceInstanceListSupplier 获取服务实例列表(通常从注册中心如 Nacos 获取)

    ReactorLoadBalancer 根据算法选择一个实例

    构建真实请求 URI(http://ip:port/path),发起调用

步骤详解:

  1. 请求拦截 :当使用 @LoadBalanced 注解的 RestTemplate 发送请求时,LoadBalancerInterceptor 会拦截该请求。

    java 复制代码
    // 伪代码示意
    public ClientHttpResponse intercept(HttpRequest request, ...) {
        URI originalUri = request.getURI();
        String serviceName = originalUri.getHost(); // 从 URI 中获取服务名
        return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, ...));
    }
  2. 获取服务实例LoadBalancerClient 通过 ServiceInstanceListSupplier 获取服务实例列表。ServiceInstanceListSupplier 可以从多种来源获取实例,最常用的是从注册中心(如 Nacos、Eureka)获取。

  3. 负载均衡选择ReactorLoadBalancer 根据配置的规则(如轮询、随机、权重)从实例列表中选择一个实例。默认实现是 RoundRobinLoadBalancer(轮询)。

  4. 发起实际请求 :将原始请求的 URI 中的服务名替换为选中的实例 IP 和端口,然后通过 RestTemplate 发起真实 HTTP 请求。

整个过程完全透明,开发者只需关注业务代码。


⭐ 实例列表是否每次都从注册中心获取?

不是 ❗

默认会经过缓存层:

复制代码
DiscoveryClientServiceInstanceListSupplier
        ↓
CachingServiceInstanceListSupplier(Caffeine)

优点:

  • 降低注册中心压力

  • 提升性能

风险:

  • 短时间内可能使用"脏实例"

3.3 Reactive 模式的本质区别

Reactive 不只是换了 WebClient,而是:

java 复制代码
Mono<Response<ServiceInstance>>

👉 实例选择本身也是异步的

优势:

  • 无阻塞

  • 更适合高并发


四、在项目中使用 Spring Cloud LoadBalancer

4.1 添加依赖

在 Spring Boot 3.x / Spring Cloud 2023.x 项目中,只需引入负载均衡器 Starter(通常通过其他依赖传递引入,但也可以显式添加):

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

如果你的项目已经使用了 Nacos 服务发现,spring-cloud-starter-alibaba-nacos-discovery 已经传递引入了 spring-cloud-starter-loadbalancer,无需重复添加。

4.2 启用负载均衡的 RestTemplate

在配置类中定义 RestTemplate Bean,并添加 @LoadBalanced 注解:

java 复制代码
@Configuration
public class AppConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

之后,在代码中就可以直接使用服务名进行调用:

java 复制代码
@RestController
public class UserController {
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/call-bank/{id}")
    public String callBank(@PathVariable Long id) {
        String url = "http://lqb-bank/api/bank/" + id; // 服务名 lqb-bank
        return restTemplate.getForObject(url, String.class);
    }
}

4.3 使用 WebClient(反应式)

若使用 WebClient,则需添加 @LoadBalancedWebClient.Builder Bean:

java 复制代码
@Configuration
public class WebClientConfig {
    @Bean
    @LoadBalanced
    public WebClient.Builder webClientBuilder() {
        return WebClient.builder();
    }
}

然后在业务代码中注入并使用:

java 复制代码
@Service
public class BankService {
    private final WebClient webClient;

    public BankService(@LoadBalanced WebClient.Builder webClientBuilder) {
        this.webClient = webClientBuilder.build();
    }

    public Mono<String> getBankData(Long id) {
        return webClient.get()
                .uri("http://lqb-bank/api/bank/" + id)
                .retrieve()
                .bodyToMono(String.class);
    }
}

4.4 负载均衡算法配置

默认负载均衡算法是轮询(RoundRobinLoadBalancer)。可以通过以下方式自定义:

  • 全局配置 :在 application.yml 中设置:

    yaml 复制代码
    spring:
      cloud:
        loadbalancer:
          configurations: random # 可选:random, round-robin, retry, 或自定义

    但目前官方提供的枚举值有限,更推荐通过 @Bean 方式自定义。

  • 自定义负载均衡规则 :实现 ReactorServiceInstanceLoadBalancer 接口,并注册为 Bean。例如,自定义一个基于权重的负载均衡器(可与 Nacos 权重配合)。

    1. 创建自定义负载均衡器类:

      java 复制代码
      public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
          private final String serviceId;
          private final ObjectProvider<ServiceInstanceListSupplier> supplierProvider;
      
          public WeightedLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> supplierProvider, String serviceId) {
              this.supplierProvider = supplierProvider;
              this.serviceId = serviceId;
          }
      
          @Override
          public Mono<Response<ServiceInstance>> choose(Request request) {
              // 从 supplierProvider 获取实例列表,根据权重选择实例
              // 具体实现略...
          }
      }
    2. 通过配置类提供 ReactorLoadBalancer<ServiceInstance> Bean:

      java 复制代码
      @Configuration
      public class LoadBalancerConfig {
          @Bean
          public ReactorLoadBalancer<ServiceInstance> weightedLoadBalancer(
                  Environment env, LoadBalancerClientFactory loadBalancerClientFactory) {
              String serviceId = env.getProperty("loadbalancer.client.name");
              return new WeightedLoadBalancer(
                      loadBalancerClientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
                      serviceId);
          }
      }
    3. 在需要应用该规则的客户端指定配置(通过 @LoadBalancerClient 或全局配置)。

  • 结合 Nacos 权重:如果使用 Nacos,可以启用 Nacos 负载均衡扩展,使其自动利用 Nacos 实例的权重配置。

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

    启用后,Nacos 客户端会将实例权重传递给 LoadBalancer,权重越高的实例被选中的概率越大。


五、负载均衡算法

默认:RoundRobinLoadBalancer 轮询算法

Spring Cloud LoadBalancer 本身不负责服务发现,它通过 ServiceInstanceListSupplier 获取实例列表。我们通常结合具体的注册中心(如 Nacos、Eureka)来实现:

  • Nacosspring-cloud-starter-alibaba-nacos-discovery 会自动配置一个 NacosServiceInstanceListSupplier,它从 Nacos 获取服务实例列表。
  • Eureka :类似的,spring-cloud-starter-netflix-eureka-client 也提供了对应的 Supplier。

这种设计将服务发现与负载均衡解耦,便于扩展。

5.1 开启服务发现

确保在启动类上添加 @EnableDiscoveryClient(虽然新版中可选),并且配置了正确的注册中心地址。以 Nacos 为例:

yaml 复制代码
spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: your-namespace-id
        group: your-group

启动后,ServiceInstanceListSupplier 即可从 Nacos 拉取服务实例。

5.2 同集群优先访问

结合 Nacos 的集群配置,可以实现优先访问同机房实例。首先为服务配置集群名称:

yaml 复制代码
spring:
  cloud:
    nacos:
      discovery:
        cluster-name: BJ

然后启用 Nacos 负载均衡扩展:

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

此时,LoadBalancer 会优先选择相同集群的实例,如果同集群无可用实例,再跨集群选择。这能有效降低跨机房调用延迟。


⭐ 关于"权重"的重要说明(容易踩坑)

❗默认不支持权重!

只有以下情况才支持:

  • 开启 Nacos 扩展
  • 或自定义实现
yaml 复制代码
spring:
  cloud:
    loadbalancer:
      nacos:
        enabled: true

六、负载均衡 + 重试机制(关键补充)

默认情况下:

❗LoadBalancer 只负责选实例,不负责重试


如何实现重试?

方式一:

  • 引入 spring-retry

方式二:

  • 使用 WebClient retry

常见策略:

  • 同实例重试

  • 切换实例重试(推荐)


⚠️ 注意

非幂等接口(如 POST)慎用重试!


七、与注册中心集成(增强说明)

LoadBalancer 本身不负责服务发现,而是通过:

text 复制代码
ServiceInstanceListSupplier

实现解耦。


⭐ 架构关系图(建议记住)

复制代码
RestTemplate/WebClient
        ↓
LoadBalancerInterceptor
        ↓
LoadBalancerClient
        ↓
ServiceInstanceListSupplier(可缓存)
        ↓
ReactorLoadBalancer
        ↓
ServiceInstance

八、生产环境常见问题(重点加分)

1️⃣ 实例雪崩(热点实例)

问题:

  • 某个实例被频繁选中

解决:

  • 随机策略
  • 权重分散

2️⃣ 脏实例问题

问题:

  • 实例已下线,但仍被调用

原因:

  • 本地缓存未更新

解决:

  • 缩短缓存时间
  • 健康检查

3️⃣ 跨机房调用

问题:

  • 延迟高

解决:

  • Nacos cluster(同集群优先)

4️⃣ 冷启动问题

问题:

  • 新实例刚上线被打满

解决:

  • 预热(warm-up)
  • 限流

九、常见问题(补充增强)

9.1 LoadBalancer 不生效

  • 未加 @LoadBalanced
  • URL 写成 IP
  • 依赖缺失

9.2 实例为空

  • 注册中心配置错误
  • namespace/group 不一致

9.3 自定义负载均衡注意点(补充)

java 复制代码
String serviceId = env.getProperty("loadbalancer.client.name");

👉 说明:

该 serviceId 由 LoadBalancerClientFactory 在运行时注入,表示当前服务名。


十、总结

Spring Cloud LoadBalancer 的核心价值在于:

1️⃣ 解耦设计

  • 服务发现(Supplier)

  • 负载均衡(Reactor)

  • 调用入口(Client)


2️⃣ 响应式支持

  • 实例选择异步化

  • 更适合高并发


3️⃣ 高扩展性

  • 可自定义算法

  • 可接入不同注册中心

  • 支持缓存与扩展机制


👉 一句话总结:

Spring Cloud LoadBalancer = 可插拔的客户端负载均衡框架(解耦 + 响应式 + 可扩展)


Spring Cloud LoadBalancer 是一个客户端负载均衡器,通过拦截请求,在本地缓存的实例列表中,使用负载均衡算法选择服务实例,并支持响应式和高度扩展的架构设计。

相关推荐
梵得儿SHI2 小时前
SpringCloud 进阶拓展:分布式事务终极解决方案 Seata AT/TCC 模式全栈实战(含生产级避坑指南)
分布式·spring·spring cloud·seata·分布式事务·tcc·tc集群部署
ℳ₯㎕ddzོꦿ࿐3 小时前
实战篇:结合 GitLab CI/CD 实现 Spring Cloud 微服务自动化部署与防坑指南
spring cloud·ci/cd·gitlab
Gofarlic_oms110 小时前
利用API实现ANSYS许可证管理自动化集成
运维·服务器·开发语言·matlab·自动化·负载均衡
user_admin_god19 小时前
SSE 流式响应 Chunk 被截断问题的排查与修复
java·人工智能·spring boot·spring·maven·mybatis
忡黑梨20 小时前
eNSP_ACL原理及应用
运维·服务器·网络·tcp/ip·github·负载均衡
TE-茶叶蛋1 天前
Spring最核心扩展点:BeanPostProcessor
java·后端·spring
大龄码农-涵哥1 天前
Spring Boot项目集成AI对话:使用Spring AI打造智能客服
人工智能·spring boot·spring
Full Stack Developme1 天前
MyBatis-Plus 注解教程
java·spring·mybatis
蓝眸少年CY1 天前
(第十五篇)spring cloud之Sentinel实现熔断与限流
数据库·spring cloud·sentinel