Appearance
LoadBalancer 详解:Spring 官方客户端负载均衡
Spring Cloud LoadBalancer 是 Spring 官方推出的客户端负载均衡器,替代了已停维的 Netflix Ribbon。基于响应式(Reactive)架构,非阻塞,性能更好,是当前 Spring Cloud 2020+ 版本的默认负载均衡方案。
一、为什么需要 LoadBalancer?
Ribbon 在 2020 年进入维护模式后,Spring 官方需要一个新的负载均衡方案。LoadBalancer 的定位是:
- 响应式架构:基于 Project Reactor,非阻塞 IO,线程资源利用率更高
- 官方维护:Spring 团队持续迭代,与 Spring Cloud 版本同步演进
- 轻量化:去掉 Ribbon 中不常用的功能,保持核心能力简洁
- 可扩展:支持自定义策略,与 Nacos 等服务发现组件深度集成
二、核心架构
LoadBalancer 的设计围绕两个核心抽象:
┌──────────────────────────┐
│ ReactiveLoadBalancer │
│ 选择实例的接口 │
└──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ ServiceInstanceListSupplier│
│ 提供实例列表的接口 │
└──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ DiscoveryClient │
│ 从注册中心获取实例 │
└──────────────────────────┘| 组件 | 接口 | 职责 |
|---|---|---|
| ReactiveLoadBalancer | ReactiveLoadBalancer<T> | 负载均衡核心接口,从实例列表中选一个 |
| ServiceInstanceListSupplier | ServiceInstanceListSupplier | 提供实例列表(从注册中心获取) |
| LoadBalancerClientFactory | LoadBalancerClientFactory | 为每个服务创建独立的 LoadBalancer 实例 |
调用流程:
1. Feign 拦截请求 → 提取服务名 "order-service"
2. LoadBalancerClientFactory 获取 order-service 对应的 LoadBalancer
3. ServiceInstanceListSupplier 从 Nacos/Consul 获取实例列表
4. ReactiveLoadBalancer 按策略选一个实例
5. 返回选中的实例,Feign 构造 HTTP 请求发送三、内置负载均衡策略
3.1 RoundRobinLoadBalancer(默认)
轮询策略,依次选择实例,简单公平。默认实现,无需额外配置。
3.2 RandomLoadBalancer
随机选择实例。需要手动配置:
java
@Configuration
@LoadBalancerClient(name = "order-service", configuration = RandomLoadBalancerConfig.class)
public class RandomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment env, LoadBalancerClientFactory factory) {
String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
factory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}3.3 NacosLoadBalancer(Nacos 权重)
当使用 Nacos 作为注册中心时,自动启用 Nacos 的权重策略。实例的权重在 Nacos 控制台配置,权重越高分配到的请求越多。
yaml
spring:
cloud:
loadbalancer:
nacos:
enabled: true权重规则: 假设 3 个实例权重分别为 5、3、2,则请求分配比例为 5:3:2。
四、自定义负载均衡策略
实现 ReactorLoadBalancer 接口即可自定义策略。以下是一个"最小连接数"策略的示例:
java
@Configuration
public class CustomLoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> minConnectionsLoadBalancer(
Environment env, LoadBalancerClientFactory factory) {
String name = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new ReactorLoadBalancer<ServiceInstance>() {
private final Map<String, AtomicInteger> connections = new ConcurrentHashMap<>();
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = factory
.getLazyProvider(name, ServiceInstanceListSupplier.class)
.getIfAvailable();
if (supplier == null) {
return Mono.empty();
}
return supplier.get().next().flatMap(instances -> {
if (instances.isEmpty()) {
return Mono.empty();
}
// 选择连接数最少的实例
ServiceInstance selected = instances.stream()
.min(Comparator.comparingInt(i ->
connections.getOrDefault(i.getHost() + ":" + i.getPort(),
new AtomicInteger(0)).get()))
.orElse(instances.get(0));
connections.computeIfAbsent(
selected.getHost() + ":" + selected.getPort(),
k -> new AtomicInteger(0)).incrementAndGet();
return Mono.just(new DefaultResponse(selected));
});
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request, ServiceInstanceList suppliers) {
return choose(request);
}
};
}
}在 Feign 接口上指定配置:
java
@FeignClient(
name = "order-service",
configuration = CustomLoadBalancerConfig.class
)
public interface OrderClient {
// ...
}五、核心配置
yaml
spring:
cloud:
loadbalancer:
retry:
enabled: true # 开启重试,失败后换实例
retry-on-all-operations: false # 只对 GET 请求重试
cache:
enabled: true # 开启实例列表缓存
ttl: 35s # 缓存过期时间
capacity: 256 # 缓存容量
health-check:
initial-delay: 0 # 首次健康检查延迟
interval: 25s # 健康检查间隔
refetch-instances-interval: 25s # 重新拉取实例列表间隔
nacos:
enabled: true # 启用 Nacos 权重策略六、与 Feign 的协作
Spring Cloud 2020+ 版本中,Feign 默认不再依赖 Ribbon,而是使用 LoadBalancer:
@FeignClient(name = "order-service")
│
▼
Feign 拦截调用 → 提取服务名 → 交给 LoadBalancer
│
▼
LoadBalancer 从 Nacos/Consul 获取实例列表
│
▼
ReactiveLoadBalancer 选择实例
│
▼
Feign 构造 HTTP 请求 → 发送到选中的实例关键差异: Feign 本身是阻塞式的,LoadBalancer 是响应式的。Spring Cloud 内部做了适配,通过 BlockingLoadBalancerClient 将响应式调用转为阻塞,对 Feign 透明。
七、从 Ribbon 迁移到 LoadBalancer
| 迁移项 | Ribbon(旧) | LoadBalancer(新) |
|---|---|---|
| 依赖 | spring-cloud-starter-netflix-ribbon | 已内置,无需额外依赖 |
| 策略配置 | order-service.ribbon.NFLoadBalancerRuleClassName | @LoadBalancerClient 注解 |
| 自定义策略 | implements IRule | implements ReactorLoadBalancer |
| 重试配置 | ribbon.MaxAutoRetries | spring.cloud.loadbalancer.retry.enabled |
| 超时配置 | ribbon.ConnectTimeout | HTTP 客户端层面配置(如 feign.client.config) |
| 权重策略 | 需自定义 | Nacos 原生支持 |
迁移步骤:
- 移除 Ribbon 依赖(Spring Cloud 2020+ 已默认移除)
- 将
ribbon.xxx配置改为spring.cloud.loadbalancer.xxx - 将自定义的
IRule实现改为ReactorLoadBalancer实现 - 启用
spring.cloud.loadbalancer.nacos.enabled=true(如使用 Nacos)
八、面试要点
Q:为什么用 LoadBalancer 替代 Ribbon?
Ribbon 已停维,基于阻塞式 IO;LoadBalancer 是 Spring 官方维护,基于响应式架构,非阻塞,性能更好,且与 Spring Cloud 版本同步演进。
Q:LoadBalancer 和 Ribbon 的区别?
| 维度 | Ribbon | LoadBalancer |
|---|---|---|
| 维护方 | Netflix(已停维) | Spring 官方(活跃) |
| 编程模型 | 阻塞式 | 响应式(Reactive) |
| 内置策略 | 7 种 | 2 种(轮询 + 随机) |
| 权重策略 | 需自定义 | Nacos 原生支持 |
| 依赖 | 独立引入 | 已内置 |
Q:Feign + LoadBalancer 如何实现负载均衡?
Feign 拦截 @FeignClient 调用,提取服务名交给 LoadBalancer。LoadBalancer 从注册中心获取实例列表,按策略选一个实例返回给 Feign。Feign 构造 HTTP 请求发送到选中的实例。整个过程通过 BlockingLoadBalancerClient 桥接响应式与阻塞式调用。
Q:如何自定义 LoadBalancer 的负载均衡策略?
实现 ReactorLoadBalancer<ServiceInstance> 接口,在 @LoadBalancerClient 或 @LoadBalancerClients 注解中指定配置类,配置类中注册自定义的 ReactorLoadBalancer Bean。
