在 Spring Cloud LoadBalancer 2025 版本中,轮询策略(RoundRobinLoadBalancer) 是默认的负载均衡算法,其核心逻辑是按照固定顺序依次将请求分发到可用的服务实例,适用于服务实例性能相近、负载较为均衡的场景。
轮询策略的核心思想是“顺序循环”:维护一个计数器(或指针),每次请求到达时,计数器递增并取模(对可用实例数量取模),得到当前应选择的实例索引,确保请求在所有可用实例间均匀分配。
例如:若有 3 个服务实例(A、B、C),则请求顺序为:A → B → C → A → B → C → ...
轮询策略作为 Spring Cloud LoadBalancer 的默认负载均衡策略,可用于以下场景:
服务实例性能差异小、负载相对均衡
轮询策略的核心假设是 “所有服务实例的处理能力与资源配置一致”,因此在实例性能同质化的集群中,能天然适配负载分配需求。
例如电商系统中的 “商品详情服务”,所有实例均部署在相同规格的服务器上(如 4C8G 云服务器),且服务逻辑无特殊资源依赖(如无本地缓存、无硬件加速需求),每个实例处理单个请求的响应时间、CPU 占用率差异小于 10%。此时轮询策略无需额外计算权重或负载,即可让每个实例接收相近数量的请求,避免因 “性能评估偏差” 导致的分配失衡。
对于完全无状态的服务(如用户登录校验服务、数据格式转换服务),实例间无需共享会话或数据,仅需根据请求量横向扩展实例数量。例如某 API 网关后端挂载 5 个相同配置的 “权限校验服务” 实例,轮询策略会按顺序将请求依次分发至 5 个实例,每个实例的请求量理论占比均为 20%,且实例扩容 / 缩容时(如从 5 个增至 8 个),策略会自动基于新的实例数量调整轮询范围,无需人工干预。
希望请求在所有实例间均匀分配,避免某一实例过载
当业务对 “实例负载均衡度” 要求较高,且不希望因算法偏向性导致某一实例长期过载时,轮询策略的 “绝对均匀性” 成为核心优势。
例如金融系统中的 “账单查询服务”,每日 9:00-18:00 期间请求量稳定在 1000 QPS,且无突发流量峰值。此时若使用 “随机策略” 可能因概率偏差导致某一实例短时间接收大量请求(如 10 个实例中某实例 1 分钟内接收 120 次请求,另一实例仅接收 80 次),而轮询策略能确保每个实例的请求量偏差控制在 “±1 次 / 分钟” 内,避免因局部过载导致的响应延迟或超时。
简单场景下的快速集成(无需复杂配置)
轮询策略作为 Spring Cloud LoadBalancer 的默认策略,无需额外引入依赖、无需编写自定义代码,仅需基础配置即可生效,特别适合快速迭代、轻量级架构的业务场景。
在业务初期(如 MVP 阶段),开发团队需快速搭建微服务架构并验证核心流程,无需在负载均衡策略上投入过多精力。例如某创业公司的 “用户注册服务”,仅部署 2 个实例用于测试,此时直接使用默认的轮询策略,无需配置权重、健康检查阈值等参数,即可实现基本的负载均衡功能,减少开发与调试时间。
在适应场景介绍了轮训策略的优点,它也存在如下缺点:
轮训负载均衡策略完全平均分配请求,未考虑实例硬件配置差异。性能弱的实例(如低 CPU、低内存)会因接收同等数量请求而过载,性能强的实例则资源闲置。
为了解决该问题,后面进化出了加权轮询策略,它通过为实例分配不同权重,实现了请求的“按需分配”,核心逻辑如下:
权重与性能挂钩:为性能强的实例设置更高权重(如 CPU 核数多的实例权重设为 5),性能弱的实例设置低权重(如 CPU 核数少的实例权重设为 1)。
请求分配按权重比例:权重决定实例接收请求的概率,权重为 5 的实例接收的请求量约为权重 1 实例的 5 倍,实现资源利用与负载压力的平衡。
兼容基础策略:当所有实例权重设置相同时,加权轮询会退化为基础轮询,保证了策略的兼容性和过渡性。
由于轮训负载均衡策略无法固定客户端与实例的对应关系(用户多次请求可能被分配到不同的机器上执行)。对于需要维持会话状态的业务(如登录状态、购物车数据),可能导致请求路由到不同实例,引发业务异常。
为了解决轮询策略中“无会话亲和性” 的问题(即无法保证同一客户端请求始终路由到同一实例),常见方案如下:
基于客户端标识的固定路由(最常用)
通过提取客户端的唯一标识(如 IP 地址、用户 ID、设备 ID 等),计算哈希值并与服务实例列表绑定,确保同一标识的请求始终路由到同一实例。该方案实现简单,无需修改服务端。如果客户端标识分布不均,可能导致某些实例负载失衡(需结合权重调整)。
会话共享(彻底消除会话依赖)
将会话状态从服务实例中剥离,存储到独立的共享存储中(如 Redis、Memcached),使客户端请求可路由到任意实例,实例通过共享存储获取会话信息。该方案彻底解耦会话与实例的绑定,支持实例动态扩缩容,适合分布式系统。但是,增加缓存依赖,需考虑缓存一致性和性能开销。
框架自带的会话亲和性配置
部分负载均衡工具或反向代理支持开箱即用的会话亲和性(Sticky Session)配置。例如,Nginx 可以通过ip_hash指令基于客户端 IP 绑定实例,或cookie指令基于自定义 Cookie 绑定:
upstream backend {
ip_hash; # 基于客户端IP哈希绑定实例
server instance1:8080;
server instance2:8080;
}
相比旧版本(如 2023 版),2025 版本的 RoundRobinLoadBalancer 做了以下优化:
响应式优先:基于 ReactorLoadBalancer 接口实现,原生支持响应式编程(如 WebFlux),避免阻塞操作。
实例动态感知:实时感知服务实例的上下线(通过 ServiceInstanceListSupplier 动态获取实例列表),自动调整轮询范围。
健康实例过滤:默认结合健康检查机制,仅对健康实例进行轮询(不健康实例自动排除)。
线程安全:使用原子类(AtomicInteger)维护计数器,确保高并发场景下的轮询顺序正确性。
RoundRobinLoadBalancer 是基于轮询(Round-Robin)算法去实现 ReactorServiceInstanceLoadBalancer 接口。而 ReactorServiceInstanceLoadBalancer 接口是 Spring Cloud LoadBalancer 中用于响应式负载均衡的核心接口,负责从服务实例列表中选择合适的实例,以支持非阻塞的服务调用。
源码如下:
package org.springframework.cloud.loadbalancer.core;
//...
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
// 日志工具,用于记录警告等信息
private static final Log log = LogFactory.getLog(RoundRobinLoadBalancer.class);
// 原子计数器,用于记录当前轮询位置,保证线程安全
final AtomicInteger position;
// 服务ID,标识当前负载均衡器对应的服务
final String serviceId;
// 单例提供者,用于获取服务实例列表供应商(ServiceInstanceListSupplier)
// 采用 SingletonSupplier 确保实例化一次,避免重复创建
private final SingletonSupplier<ServiceInstanceListSupplier> serviceInstanceListSingletonSupplier;
/**
* 初始化轮询负载均衡器
* @param serviceInstanceListSupplierProvider
* 服务实例列表供应商的提供者(ObjectProvider),用于获取可用的服务实例列表
* @param serviceId 服务ID,当前负载均衡器对应的服务名称
*/
public RoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId) {
// 调用重载构造方法,使用随机数作为初始位置(0-999之间)
this(serviceInstanceListSupplierProvider, serviceId, new Random().nextInt(1000));
}
/**
* 初始化轮询负载均衡器(指定初始位置)
* @param serviceInstanceListSupplierProvider 服务实例列表供应商的提供者
* @param serviceId 服务ID
* @param seedPosition 轮询初始位置标记,用于指定计数器的初始值
*/
public RoundRobinLoadBalancer(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider,
String serviceId, int seedPosition) {
this.serviceId = serviceId;
// 初始化服务实例列表供应商的单例提供者:
// 若获取不到可用的 Supplier,默认使用 NoopServiceInstanceListSupplier(空实现)
this.serviceInstanceListSingletonSupplier = SingletonSupplier
.of(() -> serviceInstanceListSupplierProvider.getIfAvailable(NoopServiceInstanceListSupplier::new));
// 初始化原子计数器,使用指定的初始位置
this.position = new AtomicInteger(seedPosition);
}
@SuppressWarnings("rawtypes")
@Override
// 参考 Netflix Ocelli 的轮询实现:
// https://github.com/Netflix/ocelli/blob/master/ocelli-core/
// src/main/java/netflix/ocelli/loadbalancer/RoundRobinLoadBalancer.java
public Mono<Response<ServiceInstance>> choose(Request request) {
// 从单例提供者中获取服务实例列表供应商
ServiceInstanceListSupplier supplier = serviceInstanceListSingletonSupplier.obtain();
// 调用供应商的get方法获取服务实例列表(响应式操作),并处理结果
return supplier.get(request)
.next() // 取流中的第一个元素(最新的实例列表)
// 处理实例列表,返回选中的实例
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
/**
* 处理服务实例列表响应,返回负载均衡选择结果
* @param supplier 服务实例列表供应商
* @param serviceInstances 可用的服务实例列表
* @return 包含选中实例的响应对象
*/
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
// 从实例列表中选择一个实例
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
// 若供应商实现了 SelectedInstanceCallback 接口,通知其选中的实例(用于回调扩展)
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
/**
* 核心方法:根据轮询算法从实例列表中选择一个实例
* @param instances 可用的服务实例列表
* @return 包含选中实例的响应(或空响应)
*/
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
// 若实例列表为空,返回空响应并记录警告日志
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// 若只有一个可用实例,直接返回该实例(无需轮询,优化性能)
// 注:某些供应商可能已过滤实例,此时单实例场景无需移动计数器
if (instances.size() == 1) {
return new DefaultResponse(instances.get(0));
}
// 计算当前轮询位置:
// 1. 计数器自增(保证线程安全)
// 2. 与 Integer.MAX_VALUE 按位与,忽略符号位(避免负数,确保 pos 为非负整数)
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
// 取模计算选中的实例索引(pos % 实例数量),实现循环轮询
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}源码的关键逻辑说明:
position:原子计数器,每次请求时通过 incrementAndGet() 自增,确保线程安全。并且与 Integer.MAX_VALUE 进行按位与操作,确保不是负数。
索引计算:pos % instances.size() 保证索引在 [0, 实例数量 - 1] 范围内,实现循环轮询。
实例列表来源:通过 ServiceInstanceListSupplier 获取,默认已集成健康检查过滤(仅返回健康实例)。
更多内容请查阅官方文档……