前言:一场关于"公平"分配的艺术 🎭
想象一下,你是一位餐厅经理,有三名厨师:👨🍳 大厨A(能做5道菜)、👨🍳 二厨B(能做3道菜)和 👨🍳 小厨C(能做2道菜)。现在来了10位客人,你怎么分配任务才能既发挥每个人的特长,又不让任何一位厨师累趴下?这就是负载均衡要解决的核心问题!
在分布式系统领域,权重轮询算法就像是这位聪明的餐厅经理,它能够根据服务器的"厨艺"(处理能力)分配不同比例的请求。今天,我们就来聊聊两种不同的"分配艺术":简单粗暴的非平滑权重轮询和精明圆滑的平滑权重轮询。
一、非平滑权重轮询:老实人的直球对决 ⚾
1.1 算法原理:排排坐,分果果
非平滑权重轮询算法就像是个老实巴交的分糖果小朋友:📦 "A同学5颗,B同学3颗,C同学2颗,拿好了!下一个循环继续!"
核心思想(简单到哭):
- 📊 数数总共有多少糖(总权重)
- 🔢 按照顺序发糖,发完为止
- 🔁 重新开始新一轮发糖
1.2 数学依据:蛋糕切块论 🎂
数学理论 :
把总权重想象成一个圆形蛋糕,每个服务器按照权重比例分得一块扇形区域。算法就像在蛋糕上固定一个指针,每次请求时顺时针旋转固定角度。
通俗解释 :
这就好比吃自助餐时那个固执的厨师------先给你盛满满一盘主食(连续5次给ServerA),然后是一碗汤(3次ServerB),最后是一小份甜点(2次ServerC)。简单直接,但体验嘛...你懂的!
1.3 代码实现:老实人的代码也很老实 💻
java
public Server getNextServer() {
// 简单到令人发指:数数,减减,搞定!
int index = currentIndex.updateAndGet(i -> (i + 1) % totalWeight);
for (Server server : servers) {
if (index < server.getWeight()) {
return server; // 找到啦!
}
index -= server.getWeight(); // 没找到?继续找!
}
return servers.get(0); // 理论上不会到这里,就像理论上我应该早睡了
}
1.4 执行过程图示:饥饿的大胃王比赛 🍽️
假设有三台服务器(厨师):
- 🟥 ServerA: 权重 5(大厨,能做5道菜)
- 🟦 ServerB: 权重 3(二厨,能做3道菜)
- 🟩 ServerC: 权重 2(学徒,能做2道菜)
请求分配就像这样:
请求次数 | 选中服务器 | 现场情况 |
---|---|---|
1-5 | 🟥 ServerA | 大厨:让我一次做个够! |
6-8 | 🟦 ServerB | 二厨:终于轮到我了! |
9-10 | 🟩 ServerC | 学徒:我就捡点剩的... |
11-15 | 🟥 ServerA | 大厨:怎么又是我?! |
看出来问题了吗?大厨一开始忙成狗,后面闲得慌;学徒一直饿得慌!这就是非平滑的毛病------分配是公平的,但时机完全不对!
二、平滑权重轮询:情商满分的分配大师 🎩
2.1 算法原理:细水长流的智慧 💧
平滑权重轮询就像是个精明的老茶客:每次只品一口,让每种茶叶的味道都能充分展现,不会一种茶喝到腻。
核心思想(稍微复杂但很聪明):
- 💳 给每个厨师发"积分卡"(当前权重)
- 📈 每次点菜时,所有厨师根据能力增加积分
- 🏆 选积分最高的厨师做这道菜
- 💸 被选中的厨师扣掉一些积分(相当于"消耗了体力")
- 🔁 重复这个过程
2.2 数学依据:积分卡系统 🎫
数学理论 :
每个厨师有固定能力值(权重)和当前体力值(当前权重)。每轮都增加体力值,选体力最充沛的厨师,然后扣除他的总体力消耗。
通俗解释 :
这就像玩大富翁游戏------每个人按能力获得收入(固定权重),最有钱的人请客(处理请求),请客后财富减少(减去总权重),这样大家都有请客的机会!
2.3 代码实现:聪明人的小九九 🧠
java
public Server getNextServer() {
// 第一步:给所有厨师补充体力
for (Server server : servers) {
server.setCurrentWeight(server.getCurrentWeight() + server.getWeight());
}
// 第二步:看看谁体力最充沛
Server targetServer = null;
for (Server server : servers) {
if (targetServer == null || server.getCurrentWeight() > targetServer.getCurrentWeight()) {
targetServer = server; // 就是你啦!
}
}
// 第三步:被选中的厨师消耗体力
if (targetServer != null) {
targetServer.setCurrentWeight(targetServer.getCurrentWeight() - totalWeight);
return targetServer;
}
return servers.get(0); // 备胎方案
}
2.4 执行过程图示:优雅的旋转门 🎡
同样的三位厨师,现在看看平滑算法如何展示它的社交技巧:
请求 | 增加后体力 | 选中厨师 | 调整后体力 |
---|---|---|---|
1 | 🟥 A:5, 🟦 B:3, 🟩 C:2 | 🟥 A(体力最充沛) | 🟥 A:-5, 🟦 B:3, 🟩 C:2 |
2 | 🟥 A:0, 🟦 B:6, 🟩 C:4 | 🟦 B(现在B最充沛) | 🟥 A:0, 🟦 B:-4, 🟩 C:4 |
3 | 🟥 A:5, 🟦 B:-1, 🟩 C:6 | 🟩 C(黑马出现了!) | 🟥 A:5, 🟦 B:-1, 🟩 C:-4 |
4 | 🟥 A:10, 🟦 B:2, 🟩 C:-2 | 🟥 A(Again?) | 🟥 A:0, 🟦 B:2, 🟩 C:-2 |
看看这优雅的节奏!没有人在连续加班,也没有人在长期摸鱼。就像优秀的项目经理,让每个人都能发挥所长,又不会过度劳累。
三、两种算法对比:直男与暖男的终极对决 ⚔️
3.1 性能对比表 📋
特性 | 非平滑权重轮询(直男版) | 平滑权重轮询(暖男版) |
---|---|---|
🎯 分配策略 | 排排坐,吃果果 | 细水长流,雨露均沾 |
🧩 实现难度 | 小学数学水平 | 初中数学水平 |
😫 服务器感受 | 一会儿忙死一会儿闲死 | 持续稳定输出 |
📊 适用场景 | 短期任务,不怕突发负载 | 长期运行,需要平滑分配 |
3.2 应用场景建议 💡
选择非平滑如果:
- 🔨 你的服务器都是"体育老师"------能承受突然的高强度工作
- ⚡ 你喜欢简单粗暴的解决方案
- 🕒 请求之间间隔很大,像节假日商场人流一样不连续
选择平滑如果:
- 🎨 你的服务器是"文艺青年"------需要细水长流的工作节奏
- 🌟 你想要优雅的解决方案(毕竟我们都是优雅的程序员)
- 🏃 请求连续不断,像双十一的淘宝服务器一样
四、总结:没有最好,只有最合适 ✅
权重轮询算法就像是厨房里的两位经理:
- 👔 非平滑经理:"按能力分配,别废话!"
- 🤵 平滑经理:"大家都有机会,慢慢来~"
两种算法没有绝对的好坏,只有合不合适。就像选择结婚对象,有人喜欢直接坦率的,有人喜欢温柔体贴的。
理解这两种算法的妙处,能让你在设计系统时像老练的餐厅经理一样,既能让大厨发挥特长,又能让学徒获得成长机会,最终让整个"厨房"高效运转。
记住:技术不是冷冰冰的代码,而是充满智慧的艺术。选择正确的算法,就像选择正确的生活方式------既要追求效率,也要注重体验!
温馨提示:看完这篇文章后,如果你忍不住想重新设计你的负载均衡系统,记得先备份!毕竟,就像做饭一样,理论很美好,实践可能会糊锅。
附录:完整代码示例
java
package com.sun;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 非平滑加权轮询负载均衡算法实现
* 这种算法按照权重比例分配请求,但分配不够平滑
*/
public class NonSmoothWeightedRoundRobin {
// 服务器节点类
public static class Server {
private String name;
private int weight;
public Server(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String getName() {
return name;
}
public int getWeight() {
return weight;
}
@Override
public String toString() {
return "Server{name='" + name + "', weight=" + weight + "}";
}
}
private List<Server> servers; // 服务器列表
private int totalWeight; // 总权重
private AtomicInteger currentIndex; // 当前索引
public NonSmoothWeightedRoundRobin() {
servers = new ArrayList<>();
totalWeight = 0;
currentIndex = new AtomicInteger(-1);
}
/**
* 添加服务器
* @param name 服务器名称
* @param weight 服务器权重
*/
public void addServer(String name, int weight) {
if (weight <= 0) {
throw new IllegalArgumentException("权重必须大于0");
}
servers.add(new Server(name, weight));
totalWeight += weight;
}
/**
* 获取下一台服务器 - 非平滑加权轮询算法
* 算法原理:
* 1. 计算所有服务器的总权重
* 2. 使用一个递增的计数器,对总权重取模
* 3. 遍历服务器列表,用余数依次减去每个服务器的权重
* 4. 当余数小于某个服务器的权重时,选择该服务器
* @return 被选中的服务器
*/
public Server getNextServer() {
if (servers.isEmpty()) {
throw new IllegalStateException("没有可用的服务器");
}
// 使用原子递增确保线程安全
int index = currentIndex.updateAndGet(i -> (i + 1) % totalWeight);
// 遍历服务器列表,找到对应的服务器
for (Server server : servers) {
if (index < server.getWeight()) {
return server;
}
index -= server.getWeight();
}
// 正常情况下不会执行到这里
return servers.get(0);
}
/**
* 打印服务器列表和权重信息
*/
public void printServerInfo() {
System.out.println("服务器列表:");
for (Server server : servers) {
System.out.println(" " + server.getName() + " - 权重: " + server.getWeight());
}
System.out.println("总权重: " + totalWeight);
}
/**
* 测试方法
*/
public static void main(String[] args) {
// 创建负载均衡器
NonSmoothWeightedRoundRobin lb = new NonSmoothWeightedRoundRobin();
// 添加服务器及其权重
lb.addServer("ServerA", 5); // 50%的请求
lb.addServer("ServerB", 3); // 30%的请求
lb.addServer("ServerC", 2); // 20%的请求
// 打印服务器信息
lb.printServerInfo();
// 模拟20次请求分配
System.out.println("\n请求分配情况:");
for (int i = 0; i < 20; i++) {
Server server = lb.getNextServer();
System.out.println("请求 " + (i+1) + " 分配给: " + server.getName());
}
// 验证分配比例
System.out.println("\n验证分配比例:");
int[] count = new int[3]; // 统计每个服务器的请求次数
for (int i = 0; i < 10000; i++) {
Server server = lb.getNextServer();
if ("ServerA".equals(server.getName())) {
count[0]++;
} else if ("ServerB".equals(server.getName())) {
count[1]++;
} else if ("ServerC".equals(server.getName())) {
count[2]++;
}
}
System.out.println("ServerA: " + count[0] + " 次 (" + (count[0]/100.0) + "%)");
System.out.println("ServerB: " + count[1] + " 次 (" + (count[1]/100.0) + "%)");
System.out.println("ServerC: " + count[2] + " 次 (" + (count[2]/100.0) + "%)");
}
}
package com.sun;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 非平滑加权轮询负载均衡算法实现
* 这种算法按照权重比例分配请求,但分配不够平滑
*/
public class SmoothWeightedRoundRobin {
// 服务器节点类
@Data
public static class Server {
private String name;
private int weight;
private int currentWeight;
public Server(String name, int weight, int currentWeight) {
this.name = name;
this.weight = weight;
this.currentWeight = currentWeight;
}
}
private List<Server> servers; // 服务器列表
private int totalWeight; // 总权重
private AtomicInteger currentIndex; // 当前索引
public SmoothWeightedRoundRobin() {
servers = new ArrayList<>();
totalWeight = 0;
currentIndex = new AtomicInteger(-1);
}
/**
* 添加服务器
*
* @param name 服务器名称
* @param weight 服务器权重
*/
public void addServer(String name, int weight) {
if (weight <= 0) {
throw new IllegalArgumentException("权重必须大于0");
}
servers.add(new Server(name, weight, 0));
totalWeight += weight;
}
/**
* 获取下一台服务器 - 平滑加权轮询算法
* 算法原理:
1、每个服务器有两个权重:固定权重(weight)和当前权重(current_weight)
2、每次选择时,所有服务器的当前权重增加其固定权重
3、选择当前权重最大的服务器
4、被选中的服务器的当前权重减去总权重
5、重复上述过程
*
* @return 被选中的服务器
*/
public Server getNextServer() {
if (servers.isEmpty()) {
throw new IllegalStateException("没有可用的服务器");
}
for (Server server : servers) {
server.setCurrentWeight(server.getCurrentWeight() + server.getWeight());
}
Server targetServer = null;
// 遍历服务器列表,找到对应的服务器
for (Server server : servers) {
if (targetServer==null){
targetServer=server;
continue;
}
if (server.currentWeight > targetServer.getCurrentWeight()) {
targetServer = server;
}
}
if (targetServer != null) {
targetServer.currentWeight -= totalWeight;
return targetServer;
}
// 正常情况下不会执行到这里
return servers.get(0);
}
/**
* 打印服务器列表和权重信息
*/
public void printServerInfo() {
System.out.println("服务器列表:");
for (Server server : servers) {
System.out.println(" " + server.getName() + " - 权重: " + server.getWeight());
}
System.out.println("总权重: " + totalWeight);
}
/**
* 测试方法
*/
public static void main(String[] args) {
// 创建负载均衡器
SmoothWeightedRoundRobin lb = new SmoothWeightedRoundRobin();
// 添加服务器及其权重
lb.addServer("ServerA", 5); // 50%的请求
lb.addServer("ServerB", 3); // 30%的请求
lb.addServer("ServerC", 2); // 20%的请求
// 打印服务器信息
lb.printServerInfo();
// 模拟20次请求分配
System.out.println("\n请求分配情况:");
for (int i = 0; i < 20; i++) {
Server server = lb.getNextServer();
System.out.println("请求 " + (i + 1) + " 分配给: " + server.getName());
}
// 验证分配比例
System.out.println("\n验证分配比例:");
int[] count = new int[3]; // 统计每个服务器的请求次数
for (int i = 0; i < 10000; i++) {
Server server = lb.getNextServer();
if ("ServerA".equals(server.getName())) {
count[0]++;
} else if ("ServerB".equals(server.getName())) {
count[1]++;
} else if ("ServerC".equals(server.getName())) {
count[2]++;
}
}
System.out.println("ServerA: " + count[0] + " 次 (" + (count[0] / 100.0) + "%)");
System.out.println("ServerB: " + count[1] + " 次 (" + (count[1] / 100.0) + "%)");
System.out.println("ServerC: " + count[2] + " 次 (" + (count[2] / 100.0) + "%)");
}
}