目录
[1.1 定位与演进](#1.1 定位与演进)
[1.2 核心组件分层架构](#1.2 核心组件分层架构)
[1.3 与 Ribbon 的核心对比](#1.3 与 Ribbon 的核心对比)
[二、Spring Cloud 整合实战](#二、Spring Cloud 整合实战)
[2.1 与 RestTemplate 整合](#2.1 与 RestTemplate 整合)
[第二步:配置 RestTemplate](#第二步:配置 RestTemplate)
[2.2 与 OpenFeign 整合](#2.2 与 OpenFeign 整合)
[2.3 基础配置项说明](#2.3 基础配置项说明)
[3.1 完整执行链路图](#3.1 完整执行链路图)
[3.2 拦截器入口源码](#3.2 拦截器入口源码)
[3.3 负载均衡客户端执行源码](#3.3 负载均衡客户端执行源码)
[3.4 子上下文隔离设计源码](#3.4 子上下文隔离设计源码)
[4.1 轮询策略 RoundRobinLoadBalancer(默认)](#4.1 轮询策略 RoundRobinLoadBalancer(默认))
[4.2 随机策略 RandomLoadBalancer](#4.2 随机策略 RandomLoadBalancer)
[4.3 自定义负载均衡策略实战](#4.3 自定义负载均衡策略实战)
[5.1 实例列表提供者分层架构](#5.1 实例列表提供者分层架构)
[5.2 缓存机制源码解析](#5.2 缓存机制源码解析)
[5.3 饥饿加载机制](#5.3 饥饿加载机制)
[6.1 常见踩坑点](#6.1 常见踩坑点)
[6.2 生产最佳实践](#6.2 生产最佳实践)
基于 Spring Cloud 2021.0.x 源码深度拆解,替代 Ribbon 的官方标准实现
一、核心定位与组件架构
1.1 定位与演进
Spring Cloud LoadBalancer(简称 SCL)是 Spring 官方推出的第二代负载均衡组件,用于替代已停止迭代的 Netflix Ribbon。从 Spring Cloud 2020.0.0(Ilford 版本)开始,Ribbon 被正式移除,LoadBalancer 成为微服务调用的默认负载均衡实现。
它的核心定位是客户端负载均衡:负载均衡逻辑在消费者本地执行,消费者从注册中心拉取服务列表,在本地通过算法选择目标实例发起调用,无需经过独立的负载均衡中间件,性能更高、架构更简单。
核心特性:
- 基于 Reactor 响应式编程,同时支持阻塞(RestTemplate)和非阻塞(WebClient)两种调用方式
- 与 Spring 生态深度整合,原生支持 RestTemplate、OpenFeign、WebClient
- 采用服务级子上下文隔离设计,不同服务的负载均衡配置互不影响
- 支持自定义负载均衡策略、实例过滤、缓存、重试等扩展能力
1.2 核心组件分层架构

各层职责:
- 调用入口层:RestTemplate、OpenFeign 等调用工具,对上层业务完全透明
- 拦截器层:拦截 HTTP 请求,解析服务名,触发负载均衡逻辑
- 客户端层:统一负载均衡入口,封装响应式到阻塞的适配
- 均衡算法层:核心负载均衡策略实现,如轮询、随机、权重等
- 实例供给层:负责从注册中心获取、缓存、更新服务实例列表
1.3 与 Ribbon 的核心对比
| 对比维度 | Spring Cloud LoadBalancer | Netflix Ribbon |
|---|---|---|
| 官方归属 | Spring 官方原生维护 | Netflix 开源,已停止迭代 |
| 编程模型 | 响应式 Reactor 基础,兼容阻塞 | 纯阻塞式编程 |
| 内置策略 | 轮询、随机,扩展灵活 | 轮询、随机、加权响应时间等 7 种 |
| 上下文隔离 | 基于 NamedContextFactory 子容器隔离 | 同样子容器隔离设计 |
| 生态适配 | 适配 Spring 全家桶新特性,持续迭代 | 仅适配旧版 Spring Cloud |
| 重试机制 | 依赖 Spring Retry 实现 | 内置重试能力 |
面试:Spring Cloud 为什么要替换 Ribbon?核心原因是 Ribbon 停止维护,且是阻塞式模型,无法适配 Spring WebFlux 响应式生态,官方需要一个原生、可迭代的负载均衡组件。
二、Spring Cloud 整合实战
2.1 与 RestTemplate 整合
这是最基础的使用方式,通过 @LoadBalanced 注解给 RestTemplate 赋予负载均衡能力。
第一步:引入依赖
Spring Cloud Alibaba 2.2.9 及以上版本默认已包含 LoadBalancer,无需额外引入核心包;若为纯 Spring Cloud 项目,显式引入:
XML
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
第二步:配置 RestTemplate
java
@Configuration
public class RestTemplateConfig {
/**
* @LoadBalanced 核心注解:给RestTemplate添加负载均衡拦截器
* 后续调用时直接写服务名,自动替换为实际IP端口
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
第三步:业务调用
直接用服务名替代具体的 IP 端口,负载均衡自动生效:
java
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public User getUserById(Long userId) {
// 直接写服务名 user-service,不需要写具体IP
String url = "http://user-service/user/" + userId;
return restTemplate.getForObject(url, User.class);
}
}
2.2 与 OpenFeign 整合
OpenFeign 原生适配 LoadBalancer,引入 Feign 依赖后自动生效,无需额外配置,直接写服务名即可:
java
@FeignClient("user-service")
public interface UserFeignClient {
@GetMapping("/user/{id}")
User getUserById(@PathVariable("id") Long userId);
}
Feign 内部会自动通过 LoadBalancer 选择实例发起调用,原理和 RestTemplate 一致,只是拦截器不同。
2.3 基础配置项说明
java
spring:
cloud:
loadbalancer:
cache:
enabled: true # 开启服务实例本地缓存,默认开启
ttl: 35s # 缓存过期时间,默认35秒
capacity: 256 # 缓存最大容量
eager-load:
enabled: true # 开启饥饿加载,启动时初始化负载均衡上下文
clients: user-service,order-service # 指定需要提前加载的服务名
retry:
enabled: true # 开启重试,需配合spring-retry依赖
max-retries: 0 # 同一实例重试次数
max-retries-next-server: 1 # 切换实例重试次数
三、负载均衡核心执行流程源码解析
3.1 完整执行链路图

3.2 拦截器入口源码
@LoadBalanced 注解的本质是给 RestTemplate 注入 LoadBalancerInterceptor 拦截器,这是负载均衡的触发入口。
java
// org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
// 1. 获取原始请求的URI
final URI originalUri = request.getURI();
// 2. 从URI中解析出服务名
String serviceName = originalUri.getHost();
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// 3. 调用负载均衡客户端,执行实例选择和请求执行
return this.loadBalancer.execute(serviceName,
this.requestFactory.createRequest(request, body, execution));
}
}
源码解析:
- 拦截器实现了 Spring 的
ClientHttpRequestInterceptor接口,RestTemplate 发送请求前会经过所有拦截器。 - 核心逻辑只有三步:解析服务名 → 交给负载均衡客户端 → 执行并返回结果。
@LoadBalanced注解通过LoadBalancerAutoConfiguration自动配置,给所有加了该注解的 RestTemplate Bean 自动添加这个拦截器。
3.3 负载均衡客户端执行源码
BlockingLoadBalancerClient 是阻塞模式下的默认实现,负责桥接响应式的负载均衡器和阻塞式的调用。
java
// org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
// 1. 根据服务名获取对应的负载均衡器
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
throw new IllegalStateException("No loadbalancer found for " + serviceId);
}
// 2. 响应式转阻塞:调用choose方法选择实例,阻塞等待结果
ServiceInstance serviceInstance = Mono.from(loadBalancer.choose())
.map(Response::getServer)
.block();
if (serviceInstance == null) {
throw new IllegalStateException("No instances available for " + serviceId);
}
// 3. 用选中的实例执行实际请求
return execute(serviceId, serviceInstance, request);
}
private <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
try {
// 核心:重构URI,把服务名替换成实例的IP+端口
URI uri = reconstructURI(serviceInstance, request.getUri());
// 执行实际的HTTP调用
return request.apply(serviceInstance);
} catch (Exception e) {
throw new IOException("LoadBalancer request failed", e);
}
}
关键设计:
loadBalancerClientFactory是NamedContextFactory的实现类,每个服务对应一个独立的 Spring 子上下文,不同服务的负载均衡配置、策略、缓存完全隔离,互不影响。- 底层核心均衡器是响应式的,阻塞客户端通过
.block()方法转为同步调用,兼容传统业务代码。
3.4 子上下文隔离设计源码
LoadBalancerClientFactory 继承自 NamedContextFactory,这是 Spring Cloud 通用的「命名上下文工厂」设计模式。
java
public class LoadBalancerClientFactory extends NamedContextFactory<LoadBalancerClientSpecification> {
// 学习下思想得了
// 每个服务名对应一个独立的ApplicationContext子容器
// 子容器里有自己的负载均衡器、实例列表提供者、配置Bean
// 不同服务的配置完全隔离,修改A服务的策略不影响B服务
}
四、内置负载均衡策略源码解析
4.1 轮询策略 RoundRobinLoadBalancer(默认)
版本接口差异声明 不同 Spring Cloud LoadBalancer 版本中,轮询策略实现的接口存在层级差异,但核心逻辑完全等价:
- 2.2.x 及更早版本:
RoundRobinLoadBalancer直接实现顶层泛型接口ReactorLoadBalancer<ServiceInstance>- 3.0.x 及更新版本:官方抽取了语义化子接口
ReactorServiceInstanceLoadBalancer,该接口继承自ReactorLoadBalancer<ServiceInstance>,仅做类型固化,无新增抽象方法,轮询器改为实现这个子接口。两者是纯粹的父子继承关系,内部计数器、取模算法、实例选择逻辑完全一致,不存在原理冲突。主要是学习框架的底层实现原理与思想,后续能够用到其他地方或者业务上。
轮询是 LoadBalancer 的默认策略,按顺序依次调用每个服务实例,实现简单、负载均匀,适合所有实例性能相近的场景。
java
// org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer
public class RoundRobinLoadBalancer implements ReactiveLoadBalancer<ServiceInstance> {
// 原子整型计数器,保证多线程下的线程安全
private final AtomicInteger position = new AtomicInteger(0);
private final ServiceInstanceListSupplier serviceInstanceListSupplier;
private final String serviceId;
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
// 1. 从实例提供者获取当前可用的服务实例列表
return serviceInstanceListSupplier.get()
.next()
.map(serviceInstances -> processInstanceResponse(serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 2. 原子类自增,获取当前计数位置
int pos = Math.abs(this.position.incrementAndGet());
// 3. 对实例数取模,得到本次选中的实例索引
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
源码深度解析:
- 用
AtomicInteger原子类实现计数器,CAS 无锁操作保证高并发下的线程安全,不会出现两个请求拿到同一个索引的问题。 - 计数器是持续递增的,不是循环归零,通过取模运算实现循环效果,避免归零瞬间的并发问题。
- 实例列表动态变化时(上下线),取模的分母会变化,可能出现短暂的调用不均匀,属于正常现象。
4.2 随机策略 RandomLoadBalancer
从可用实例列表中随机选择一个,流量越大约均匀,适合实例性能差异不大的场景。
java
public class RandomLoadBalancer implements ReactiveLoadBalancer<ServiceInstance> {
private final ServiceInstanceListSupplier serviceInstanceListSupplier;
private final Random random = new Random();
private Response<ServiceInstance> processInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 生成随机索引,范围是0到实例数-1
int index = random.nextInt(instances.size());
return new DefaultResponse(instances.get(index));
}
}
4.3 自定义负载均衡策略实战
生产环境经常需要基于权重、灰度标签、一致性哈希等自定义策略,以权重策略为例,完整实现如下:
第一步:自定义负载均衡器
java
public class WeightLoadBalancer implements ReactiveLoadBalancer<ServiceInstance> {
private final ServiceInstanceListSupplier supplier;
public WeightLoadBalancer(ServiceInstanceListSupplier supplier) {
this.supplier = supplier;
}
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.get().next().map(this::selectByWeight);
}
private Response<ServiceInstance> selectByWeight(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
return new EmptyResponse();
}
// 1. 计算总权重
int totalWeight = 0;
for (ServiceInstance instance : instances) {
// 从实例元数据中读取权重,默认权重为1
String weightStr = instance.getMetadata().get("weight");
int weight = StringUtils.hasText(weightStr) ? Integer.parseInt(weightStr) : 1;
totalWeight += weight;
}
// 2. 生成0到总权重之间的随机数
int random = ThreadLocalRandom.current().nextInt(totalWeight);
int currentWeight = 0;
// 3. 权重轮询:累加权重,命中区间则返回对应实例
for (ServiceInstance instance : instances) {
String weightStr = instance.getMetadata().get("weight");
int weight = StringUtils.hasText(weightStr) ? Integer.parseInt(weightStr) : 1;
currentWeight += weight;
if (random < currentWeight) {
return new DefaultResponse(instance);
}
}
// 兜底返回第一个实例
return new DefaultResponse(instances.get(0));
}
}
第二步:注册自定义策略
java
// 注意:不要加@Configuration注解,避免被全局扫描
// 这是单个服务的子上下文配置
public class WeightLoadBalancerConfig {
@Bean
public ReactiveLoadBalancer<ServiceInstance> loadBalancer(Environment environment,
ServiceInstanceListSupplier supplier) {
String serviceId = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new WeightLoadBalancer(supplier);
}
}
第三步:指定服务使用自定义策略
java
@Configuration
@LoadBalancerClient(value = "user-service", configuration = WeightLoadBalancerConfig.class)
public class LoadBalancerConfig {
// 给user-service服务单独指定权重负载均衡策略
// 其他服务继续使用默认轮询策略
}
五、服务实例列表获取与缓存机制
5.1 实例列表提供者分层架构
ServiceInstanceListSupplier 是实例供给的顶层接口,采用装饰器模式层层增强,形成完整的获取、过滤、缓存链路:
- DiscoveryClientServiceInstanceListSupplier:最底层,通过 DiscoveryClient 从注册中心(Nacos/Eureka)拉取原始服务列表
- HealthCheckServiceInstanceListSupplier:健康检查过滤,剔除不健康的实例
- CachingServiceInstanceListSupplier:缓存装饰器,给实例列表增加本地缓存能力
- ZonePreferenceServiceInstanceListSupplier:同机房优先过滤,优先选择同可用区的实例
设计思想:用装饰器模式替代继承,灵活组合各种能力,需要什么功能就加对应的装饰器,扩展性极强。
5.2 缓存机制源码解析
默认开启本地缓存,避免每次调用都去注册中心拉取实例,大幅降低注册中心压力,提升调用性能。
java
// CachingServiceInstanceListSupplier 核心逻辑
public Flux<List<ServiceInstance>> get() {
// 1. 先查缓存
Cache cache = cacheManager.getCache(serviceId);
if (cache != null) {
List<ServiceInstance> cached = cache.get(serviceId, List.class);
if (cached != null) {
return Flux.just(cached);
}
}
// 2. 缓存未命中,调用下层提供者拉取
return delegate.get()
.doOnNext(instances -> {
// 3. 拉取到后写入缓存,设置过期时间
if (cache != null) {
cache.put(serviceId, instances);
}
});
}
特性说明:
- 默认缓存过期时间 35 秒,过期后下次调用才重新拉取,不是主动定时更新。
- 缓存的好处是性能高、注册中心故障不影响调用;坏处是实例上下线有延迟,最长延迟等于缓存过期时间。
- 生产环境权衡:对扩缩容实时性要求高的场景调小 TTL,对稳定性要求高的场景调大 TTL。
5.3 饥饿加载机制
默认情况下,负载均衡的子上下文是懒加载的:第一次调用某个服务时,才会创建该服务的 Spring 子上下文,初始化负载均衡器、实例缓存等组件,导致第一次调用耗时明显变长。
开启饥饿加载后,应用启动时就会初始化所有指定服务的负载均衡上下文,消除首次调用延迟:
java
spring:
cloud:
loadbalancer:
eager-load:
enabled: true
clients: user-service,order-service,goods-service
生产:核心链路的服务全部开启饥饿加载,避免冷启动时的超时、慢调用问题。
六、实战踩坑与最佳实践
6.1 常见踩坑点
-
依赖冲突导致负载均衡失效 项目中同时引入了 Ribbon 和 LoadBalancer 依赖,或者旧版 starter 自带 Ribbon,会导致负载均衡行为异常、策略不生效。解决方式是排除 Ribbon 依赖,统一使用 LoadBalancer。
-
实例宕机后仍被调用 本地缓存默认 35 秒过期,实例宕机后最长 35 秒内还会被负载均衡选中,导致调用失败。 解决方案:调小缓存 TTL;配合重试机制,失败后自动切换实例;配合健康检查过滤器主动探活。
-
首次调用超时 懒加载导致第一次调用需要初始化子上下文,耗时几百毫秒到几秒,高并发下容易触发超时。 解决方案:核心服务开启饥饿加载,启动时提前初始化。
-
重试次数配置错误
max-retries是同一实例的重试次数,max-retries-next-server是切换实例的次数,总调用次数 = (1 + max-retries) * (1 + max-retries-next-server)。配置过大会导致雪崩效应,幂等性差的接口要谨慎开启重试。 -
自定义配置被全局扫描 自定义负载均衡配置类如果加了
@Configuration且被主类包扫描到,会变成全局配置,所有服务都生效,违背隔离设计。 解决方案:自定义配置类不要加@Configuration,或者放到扫描范围之外。
关于第4点重试次数:
在 Ribbon 的 RetryRule 或 Spring Retry 的 RetryTemplate 实现中,重试是双层循环结构:
-
外层循环 :控制"切换实例"(
next-server) -
内层循环 :控制"当前实例重试"(
retries)
代码逻辑如下:
java
for (int i = 0; i <= maxRetriesNextServer; i++) { // 外层:换机器
// 选一台机器
for (int j = 0; j <= maxRetries; j++) { // 内层:死磕这台机器
try {
// 发起调用
return result;
} catch (Exception e) {
if (j == maxRetries) break; // 内层结束,外层换机
}
}
}
因为是两层 for 循环嵌套,总执行次数是 (1 + 内层重试) × (1 + 外层换机)
6.2 生产最佳实践
- 核心服务开启饥饿加载,消除首次调用延迟
- 合理设置缓存 TTL,平衡实时性与性能,常规业务 10~30 秒即可
- 幂等接口开启重试,非幂等接口关闭重试,避免数据重复
- 多机房部署时开启同机房优先,降低跨机房调用延迟
- 自定义策略优先通过装饰器扩展,不直接修改核心逻辑
- 配合注册中心的健康检查,双重过滤不健康实例,提升调用成功率
七、面试速记总结
- 定位:Spring Cloud LoadBalancer 是官方替代 Ribbon 的客户端负载均衡组件,支持阻塞和响应式,基于子上下文实现服务隔离。
- 执行流程:拦截器拦截请求 → 解析服务名 → 从子上下文获取负载均衡器 → 拉取实例列表 → 算法选实例 → 重构 URL → 发起调用。
- 默认策略:默认轮询 RoundRobin,用 AtomicInteger 原子自增取模实现,线程安全;内置随机策略,支持自定义扩展。
- 缓存机制:默认本地缓存 35 秒,装饰器模式实现,性能高但有延迟;可通过 TTL 调整实时性。
- 子上下文设计:每个服务对应独立 Spring 子容器,配置隔离、故障隔离;饥饿加载解决首次调用慢的问题。
- 核心设计模式:拦截器模式、装饰器模式(实例供给链)、工厂模式(命名上下文工厂)、策略模式(负载均衡算法)。