当前位置: 首页 > java >正文

负载均衡之平滑加权轮询(Smooth Weighted Round Robin)详解与实现

在分布式系统、负载均衡器、RPC 框架中,常常需要在多个服务实例之间进行流量分配。如果实例的能力不同,就需要根据“权重”来调度请求。常见的负载算法有:

  • 普通轮询 (Round Robin, RR):所有实例均匀分配请求;
  • 加权随机 (Weighted Random):按权重比例随机选择实例;
  • 加权轮询 (Weighted Round Robin, WRR):按照权重比例循环选择实例。

但是,传统的 WRR 有个缺点:容易出现高权重实例连续被选中的情况,短时间内分布不均衡。

为了解决这个问题,Nginx 提出了 平滑加权轮询(Smooth Weighted Round Robin, SWRR) 算法。


1. 为什么需要 SWRR?

举个例子:有三个实例,权重分别是 A=5, B=1, C=1。

传统 WRR 的可能调度序列:

A, A, A, A, A, B, C

A 连续五次,虽然长期比例正确,但短期内不平滑,可能导致抖动。

SWRR 的调度序列:

A, A, B, A, C, A, A

同样保证了比例 5:1:1,但分布更加均匀,用户体验更稳定。


2. SWRR 算法原理

SWRR 的核心思想是:
每个实例维护一个动态权重 currentWeight,每次调用时:

  1. 累加:对所有实例执行 currentWeight += weight
  2. 选择:选择 currentWeight 最大的实例作为本次结果;
  3. 扣减:对选中的实例执行 currentWeight -= totalWeighttotalWeight 是所有权重之和)

这样不断迭代,currentWeight 会在一个“周期”内上下波动,保证:

  • 长期调度比例符合权重;
  • 短期分布尽量均匀;
  • 周期结束时,currentWeight 回到全 0,进入下一轮。

3. 示例推演(A=5, B=1, C=1)

初始状态:(A=0, B=0, C=0),总权重=7。

调用次序累加后 (A,B,C)选中扣减后 (A,B,C)
1(5,1,1)A(-2,1,1)
2(3,2,2)A(-4,2,2)
3(1,3,3)B(1,-4,3)
4(6,-3,4)A(-1,-3,4)
5(4,-2,5)C(4,-2,-2)
6(9,-1,-1)A(2,-1,-1)
7(7,0,0)A(0,0,0)
初始: (0,0,0)
累加: (5,1,1)
选择 A → 减 7 → (-2,1,1)
累加: (3,2,2)
选择 A → 减 7 → (-4,2,2)
累加: (1,3,3)
选择 B → 减 7 → (1,-4,3)
累加: (6,-3,4)
选择 A → 减 7 → (-1,-3,4)
累加: (4,-2,5)
选择 C → 减 7 → (4,-2,-2)
累加: (9,-1,-1)
选择 A → 减 7 → (2,-1,-1)
累加: (7,0,0)
选择 A → 减 7 → (0,0,0)

可以看到:

  • 7 次调用刚好符合权重比例 (5:1:1);
  • 结束后状态回到 (0,0,0),进入下一个周期;
  • 周期性地产生 A, A, B, A, C, A, A 这样平滑的序列。

4. Java 实现

下面给出一个 Java 版的 SWRR 负载均衡器,它能够在多实例(ModelInstance)之间,按照各自权重 长期比例接近、短期分布尽量平滑 地分配请求。

核心处理流程:

  1. 每次选择前:对每个实例的 currentWeight += weight。

  2. 选出 currentWeight 最大的实例 best。

  3. 令 best.currentWeight -= totalWeight(totalWeight = Σ weight)。

  4. 下次选择重复 1~3。

  5. 这样就能得到“比例接近权重、且不突发”的平滑分布(如 5:1:1 会呈现 A,A,B,A,C,A,A 这种节奏)。

所有权重之和 ≤ 0 时,这里退化为 普通轮询(Round Robin)

import per.mjn.route_rule.domain.entity.ModelInstance;import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;/*** 平滑加权轮询(Smooth Weighted Round Robin)* 按模型粒度加锁,线程安全。*/
public class WeightedRoundRobinLoadBalancer {private static class Weighted {int weight;             // 固定权重int currentWeight = 0;  // 动态权重ModelInstance instance;}/** 每个模型的状态 */private final Map<String, List<Weighted>> stateMap = new ConcurrentHashMap<>();/** 每个模型一把锁,避免全局阻塞 */private final Map<String, ReentrantLock> locks = new ConcurrentHashMap<>();/** 当所有权重 <= 0 时,退化为普通轮询 */private final Map<String, AtomicInteger> rrCounters = new ConcurrentHashMap<>();public ModelInstance choose(List<ModelInstance> instances, String modelKey) {if (instances == null || instances.isEmpty()) return null;final String key = String.valueOf(modelKey);ReentrantLock lock = locks.computeIfAbsent(key, k -> new ReentrantLock());lock.lock();try {instances.sort(Comparator.comparing(ModelInstance::getId));// 初始化/刷新状态List<Weighted> state = stateMap.compute(key, (k, old) -> {if (old == null || old.size() != instances.size() || !equalsByWeight(old, instances)) {return build(instances);}return old;});int totalWeight = state.stream().mapToInt(w -> w.weight).sum();if (totalWeight <= 0) {AtomicInteger c = rrCounters.computeIfAbsent(key, k -> new AtomicInteger(0));int idx = Math.floorMod(c.getAndIncrement(), state.size());return state.get(idx).instance;}Weighted best = null;for (Weighted w : state) {w.currentWeight += w.weight;if (best == null || w.currentWeight > best.currentWeight) {best = w;}}best.currentWeight -= totalWeight;return best.instance;} finally {lock.unlock();}}private boolean equalsByWeight(List<Weighted> old, List<ModelInstance> instances) {if (old.size() != instances.size()) return false;for (int i = 0; i < old.size(); i++) {if (!Objects.equals(old.get(i).instance.getId(), instances.get(i).getId())) return false;if (old.get(i).weight != safeWeight(instances.get(i).getWeight())) return false;}return true;}private List<Weighted> build(List<ModelInstance> instances) {List<Weighted> list = new ArrayList<>();for (ModelInstance ins : instances) {Weighted w = new Weighted();w.weight = safeWeight(ins.getWeight());w.instance = ins;list.add(w);}return list;}private int safeWeight(Integer w) {return (w == null || w < 0) ? 1 : w;}
}

流程图如下:

开始: choose 方法
instances 是否为空?
返回 null
结束
按实例ID排序 保证稳定顺序
stateMap 是否存在 且与实例及权重一致?
重建状态: currentWeight 置零 并载入权重
读取旧状态
计算总权重: 所有权重之和
总权重 小于等于 0 ?
退化为普通轮询
计算 idx: 计数器的值对 N 取模
返回第 idx 个实例
遍历: 每项的 currentWeight 加上自身权重
选出 currentWeight 最大的实例 记为 best
将 best 的 currentWeight 减去 总权重
返回 best 实例

5. 特点与优势

  • 平滑性:避免了高权重实例集中出现,调度更加均匀;
  • 比例准确:长期来看,命中次数严格符合权重;
  • 周期性:每次执行“权重和”次调用后,状态回到初始值,进入下一个周期;
  • 适合高并发:通过按模型粒度加锁,避免全局瓶颈。

6. 总结

  • 普通轮询:公平,但无法体现实例差异。
  • 加权随机:简单,可能有抖动。
  • 加权轮询:比例正确,但不够平滑。
  • 平滑加权轮询 (SWRR):既保证比例,又让分布均匀,是生产环境负载均衡的常用算法(Nginx、Dubbo 等框架都在用)。

SWRR 看似简单,实则优雅。它通过“累加 + 扣总和”的小技巧,把“按比例”与“平滑性”完美结合,是一个非常值得学习的经典调度算法。如果需要一个既能体现实例权重,又能保持稳定性的算法,那么SWRR 不失为一种好的选择

http://www.xdnf.cn/news/18890.html

相关文章:

  • MIME类型与文件上传漏洞 - 网络安全视角
  • AI解决生活小事系列——用AI给我的电脑做一次“深度体检”
  • Windows下的异步IO通知模型
  • 一款基于 .NET 开源、功能强大的 Windows 搜索工具
  • C# .NET支持多线程并发的压缩组件
  • 2026 济南玉米深加工展:探索淀粉技术突破与可持续发展解决方案
  • 你真的了解操作系统吗?
  • Feign 调用为服务报 `HardCodedTarget(type=xxxClient, name=xxxfile, url=http://file)`异常
  • 大模型入门实战 | 基于 YOLO 数据集微调 Qwen2.5-VL-3B-Instruct 的目标检测任务
  • YggJS RButton 按钮组件 v1.0.0 使用教程
  • 【vue eslint】报错:Component name “xxxx“ should always be multi-word
  • 云上“安全管家”|移动云以云安全中心为企业数字化升级保驾护航
  • 科技信息差(8.26)
  • 【软考论文】论静态测试方法及其应用
  • PortSwigger靶场之Blind SQL injection with out-of-band interaction通关秘籍
  • 软考-系统架构设计师 计算机系统基础知识详细讲解
  • 【46页PPT】AI智能中台用ABC+IOT重新定义制造(附下载方式)
  • 相机Camera日志实例分析之十五:相机Camx【照片后置HDR拍照】单帧流程日志详解
  • 2-5 倍性能提升,30% 成本降低,阿里云 SelectDB 存算分离架构助力波司登集团实现降本增效
  • 支持向量机核心知识总结
  • Java大厂面试实战:从Spring Boot到微服务架构的深度剖析
  • 宠物智能,是「养宠自由」还是「焦虑税」?
  • 【分享开题答辩过程】一辆摩托车带来的通关副本攻略----《摩托车网上销售系统》开题答辩!!
  • Stream流中的Map与flatMap的区别
  • AI安全监控与人才需求的时间悖论(对AI安全模型、AI安全人才需求的一些思考)
  • 前沿技术借鉴研讨-2025.8.26(多任务分类/预测)
  • 基于CentOS7:Linux服务器的初始化流程
  • 从零开始学MCP(7) | 实战:用 MCP 构建论文分析智能体
  • Java 大视界 -- Java 大数据机器学习模型在金融市场波动预测与资产配置动态调整中的应用
  • Docker:部署Java后端