写在前面:说实话,做分布式系统这些年,我见过太多人把负载均衡当成"随机分配"来用。3台服务器,A是8核32G,B和C是4核16G,如果平均分配流量,A只用了一半性能,B和C已经被压垮了。这就像公司分红------能力强的多分点,能力弱的少分点,而不是大家平分。这篇文章把加权轮询算法的原理、实现和踩坑经验整理出来,看完你就能手写一个Nginx同款平滑加权轮询。

文章目录
-
- 一、为什么需要加权轮询?
-
- [1.1 一个真实的线上场景](#1.1 一个真实的线上场景)
- [1.2 加权轮询的核心价值](#1.2 加权轮询的核心价值)
- [1.3 生活类比:公司分红](#1.3 生活类比:公司分红)
- 二、轮询算法演进
-
- [2.1 四种算法对比](#2.1 四种算法对比)
- [2.2 随机算法](#2.2 随机算法)
- [2.3 普通轮询](#2.3 普通轮询)
- 三、加权轮询原理
-
- [3.1 核心思想](#3.1 核心思想)
- [3.2 基于随机数的加权轮询实现](#3.2 基于随机数的加权轮询实现)
- [3.3 这个方案的缺陷](#3.3 这个方案的缺陷)
- 四、平滑加权轮询算法(Nginx算法)
-
- [4.1 算法原理](#4.1 算法原理)
- [4.2 详细推演](#4.2 详细推演)
- [4.3 完整Java实现](#4.3 完整Java实现)
- [4.4 为什么叫"平滑"?](#4.4 为什么叫"平滑"?)
- [4.5 性能优化:去掉synchronized](#4.5 性能优化:去掉synchronized)
- 五、Dubbo和Ribbon的加权轮询实现
-
- [5.1 Dubbo的RoundRobinLoadBalance](#5.1 Dubbo的RoundRobinLoadBalance)
- [5.2 Ribbon的WeightedResponseTimeRule](#5.2 Ribbon的WeightedResponseTimeRule)
- [5.3 权重动态调整](#5.3 权重动态调整)
- 六、踩坑指南
- 七、问题与解答
- 八、面试高频考点汇总
- 九、模拟面试官提问和参考答案
- 十、互动话题
- 十一、参考资料
一、为什么需要加权轮询?
1.1 一个真实的线上场景
我们系统有3台应用服务器:
| 服务器 | CPU | 内存 | 权重 |
|---|---|---|---|
| A | 8核 | 32G | 5 |
| B | 4核 | 16G | 1 |
| C | 4核 | 16G | 1 |
如果平均分配流量,A只用了一半性能,B和C已经被压垮了。
结果是:B和C频繁GC、响应变慢,甚至OOM,而A还在"摸鱼"。
1.2 加权轮询的核心价值
- 按能力分配:高性能服务器承担更多流量
- 资源利用率最大化:避免"旱的旱死,涝的涝死"
- 系统稳定性:防止低配服务器被压垮
1.3 生活类比:公司分红
公司年底分红,不可能大家平分。
业绩好的团队多分点,业绩一般的团队少分点。
加权轮询就是这个逻辑------按"能力"(权重)分配,而不是按"人头"平均分配。
二、轮询算法演进
2.1 四种算法对比
| 算法 | 实现复杂度 | 均匀度 | 是否考虑性能差异 | 适用场景 |
|---|---|---|---|---|
| 随机算法 | 低 | 一般 | 否 | 服务器性能相近 |
| 普通轮询 | 低 | 好 | 否 | 服务器性能完全相同 |
| 加权轮询 | 中 | 好 | 是 | 服务器性能不同 |
| 平滑加权轮询 | 中 | 极好 | 是 | 高并发,要求均匀 |
2.2 随机算法
java
/**
* 随机负载均衡
* 简单但不均匀,可能连续命中同一台服务器
*/
public class RandomLoadBalance {
private List<String> servers;
private Random random = new Random();
public RandomLoadBalance(List<String> servers) {
this.servers = servers;
}
public String select() {
int index = random.nextInt(servers.size());
return servers.get(index);
}
}
缺点:可能连续几次都选中同一台服务器,流量不均匀。
2.3 普通轮询
java
/**
* 普通轮询
* 按顺序依次分配,不考虑服务器性能差异
*/
public class RoundRobinLoadBalance {
private List<String> servers;
private AtomicInteger index = new AtomicInteger(0);
public RoundRobinLoadBalance(List<String> servers) {
this.servers = servers;
}
public String select() {
int current = index.getAndIncrement() % servers.size();
return servers.get(current);
}
}
缺点:A、B、C三台机器轮流分配,不管A是8核还是2核。
三、加权轮询原理
3.1 核心思想
总权重 = w1 + w2 + ... + wn
每个服务器的选中概率 = wi / 总权重
示例:
服务器A权重5,B权重1,C权重1,总权重 = 7。
- A的选中概率 = 5/7 ≈ 71%
- B的选中概率 = 1/7 ≈ 14%
- C的选中概率 = 1/7 ≈ 14%
3.2 基于随机数的加权轮询实现
java
import java.util.*;
/**
* 基于随机数的加权轮询
* 把权重映射到区间,随机数落在哪个区间就选哪个
*/
public class WeightedRandomLoadBalance {
// 服务器列表
private List<Server> servers;
// 总权重
private int totalWeight;
// 随机数生成器
private Random random = new Random();
public WeightedRandomLoadBalance(List<Server> servers) {
this.servers = servers;
this.totalWeight = servers.stream().mapToInt(Server::getWeight).sum();
}
/**
* 选择服务器
* 思路:生成一个[0, totalWeight)的随机数,看它落在哪个区间
*/
public Server select() {
int randomValue = random.nextInt(totalWeight);
int currentWeight = 0;
for (Server server : servers) {
currentWeight += server.getWeight();
if (randomValue < currentWeight) {
return server;
}
}
// 兜底,理论上不会走到这里
return servers.get(servers.size() - 1);
}
// 测试
public static void main(String[] args) {
List<Server> servers = Arrays.asList(
new Server("A", "192.168.1.1", 5),
new Server("B", "192.168.1.2", 1),
new Server("C", "192.168.1.3", 1)
);
WeightedRandomLoadBalance lb = new WeightedRandomLoadBalance(servers);
// 模拟1000次请求,统计每台服务器的选中次数
Map<String, Integer> countMap = new HashMap<>();
for (int i = 0; i < 1000; i++) {
Server server = lb.select();
countMap.merge(server.getName(), 1, Integer::sum);
}
System.out.println("=== 基于随机数的加权轮询结果 ===");
countMap.forEach((name, count) ->
System.out.println(name + ": " + count + " 次 (" + (count * 100.0 / 1000) + "%)"));
}
}
/**
* 服务器节点
*/
class Server {
private String name;
private String ip;
private int weight;
public Server(String name, String ip, int weight) {
this.name = name;
this.ip = ip;
this.weight = weight;
}
public String getName() { return name; }
public String getIp() { return ip; }
public int getWeight() { return weight; }
@Override
public String toString() {
return name + "(" + ip + ", weight=" + weight + ")";
}
}
运行结果示例:
=== 基于随机数的加权轮询结果 ===
A: 713 次 (71.3%)
B: 145 次 (14.5%)
C: 142 次 (14.2%)
3.3 这个方案的缺陷
随机数方案的问题是:不均匀。
短时间内可能连续选中A,也可能连续选中B。
高并发场景下,这种"抖动"会导致某台服务器瞬间被打满。
四、平滑加权轮询算法(Nginx算法)
4.1 算法原理
Nginx使用的平滑加权轮询算法,核心思想:避免短时间内流量集中在一台服务器。
算法步骤(每轮):
- 当前权重 += 原始权重
- 选中当前权重最大的服务器
- 选中后,当前权重 -= 总权重
4.2 详细推演
初始状态:A权重5,B权重1,C权重1,总权重 = 7。
| 轮次 | 当前权重(加原始权重后) | 选中 | 选中后当前权重 |
|---|---|---|---|
| 初始 | 0, 0, 0 | - | 0, 0, 0 |
| 第1轮 | 5, 1, 1 | A(5最大) | 5-7, 1, 1 = -2, 1, 1 |
| 第2轮 | -2+5, 1+1, 1+1 = 3, 2, 2 | A(3最大) | 3-7, 2, 2 = -4, 2, 2 |
| 第3轮 | -4+5, 2+1, 2+1 = 1, 3, 3 | B(3最大,平局取第一个) | 1, 3-7, 3 = 1, -4, 3 |
| 第4轮 | 1+5, -4+1, 3+1 = 6, -3, 4 | A(6最大) | 6-7, -3, 4 = -1, -3, 4 |
| 第5轮 | -1+5, -3+1, 4+1 = 4, -2, 5 | C(5最大) | 4, -2, 5-7 = 4, -2, -2 |
| 第6轮 | 4+5, -2+1, -2+1 = 9, -1, -1 | A(9最大) | 9-7, -1, -1 = 2, -1, -1 |
| 第7轮 | 2+5, -1+1, -1+1 = 7, 0, 0 | A(7最大) | 7-7, 0, 0 = 0, 0, 0 |
7轮选中结果:A-A-B-A-C-A-A
A被选中5次(5/7),B被选中1次(1/7),C被选中1次(1/7)。
符合权重比例,且不会连续出现A-A-A这种情况!
4.3 完整Java实现
java
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 平滑加权轮询负载均衡(Nginx算法)
* 避免短时间内流量集中在一台服务器
*/
public class SmoothWeightedRoundRobin {
// 服务器节点列表
private List<Node> nodes;
// 总权重
private int totalWeight;
public SmoothWeightedRoundRobin(List<Node> nodes) {
this.nodes = new ArrayList<>(nodes);
this.totalWeight = nodes.stream().mapToInt(Node::getWeight).sum();
}
/**
* 选择服务器
* 算法:每轮当前权重 += 原始权重,选最大的,选中后 -= 总权重
*/
public synchronized Node select() {
Node selected = null;
int maxCurrentWeight = Integer.MIN_VALUE;
for (Node node : nodes) {
// 当前权重 += 原始权重
node.currentWeight += node.weight;
// 找当前权重最大的
if (node.currentWeight > maxCurrentWeight) {
maxCurrentWeight = node.currentWeight;
selected = node;
}
}
// 选中后,当前权重 -= 总权重
if (selected != null) {
selected.currentWeight -= totalWeight;
}
return selected;
}
/**
* 打印当前状态(调试用)
*/
public void printStatus() {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < nodes.size(); i++) {
sb.append(nodes.get(i).name).append("=").append(nodes.get(i).currentWeight);
if (i < nodes.size() - 1) sb.append(", ");
}
sb.append("]");
System.out.println(sb);
}
public static void main(String[] args) {
List<Node> nodes = Arrays.asList(
new Node("A", "192.168.1.1", 5),
new Node("B", "192.168.1.2", 1),
new Node("C", "192.168.1.3", 1)
);
SmoothWeightedRoundRobin lb = new SmoothWeightedRoundRobin(nodes);
System.out.println("=== 平滑加权轮询详细过程 ===");
System.out.println("初始权重:A=5, B=1, C=1,总权重=7");
System.out.println();
Map<String, Integer> countMap = new LinkedHashMap<>();
countMap.put("A", 0);
countMap.put("B", 0);
countMap.put("C", 0);
// 模拟14轮(2个周期)
for (int i = 1; i <= 14; i++) {
Node selected = lb.select();
countMap.merge(selected.name, 1, Integer::sum);
System.out.println("第" + String.format("%2d", i) + "轮:选中 " + selected.name +
",当前状态:A=" + nodes.get(0).currentWeight +
", B=" + nodes.get(1).currentWeight +
", C=" + nodes.get(2).currentWeight);
}
System.out.println();
System.out.println("=== 统计结果 ===");
countMap.forEach((name, count) ->
System.out.println(name + ": " + count + " 次"));
}
/**
* 节点内部类
*/
static class Node {
String name;
String ip;
int weight; // 原始权重(不变)
int currentWeight; // 当前权重(动态变化)
public Node(String name, String ip, int weight) {
this.name = name;
this.ip = ip;
this.weight = weight;
this.currentWeight = 0;
}
public int getWeight() {
return weight;
}
@Override
public String toString() {
return name + "(" + ip + ")";
}
}
}
运行结果:
=== 平滑加权轮询详细过程 ===
初始权重:A=5, B=1, C=1,总权重=7
第 1轮:选中 A,当前状态:A=-2, B=1, C=1
第 2轮:选中 A,当前状态:A=-4, B=2, C=2
第 3轮:选中 B,当前状态:A=1, B=-4, C=3
第 4轮:选中 A,当前状态:A=-1, B=-3, C=4
第 5轮:选中 C,当前状态:A=4, B=-2, C=-2
第 6轮:选中 A,当前状态:A=2, B=-1, C=-1
第 7轮:选中 A,当前状态:A=0, B=0, C=0
第 8轮:选中 A,当前状态:A=-2, B=1, C=1
第 9轮:选中 A,当前状态:A=-4, B=2, C=2
第10轮:选中 B,当前状态:A=1, B=-4, C=3
第11轮:选中 A,当前状态:A=-1, B=-3, C=4
第12轮:选中 C,当前状态:A=4, B=-2, C=-2
第13轮:选中 A,当前状态:A=2, B=-1, C=-1
第14轮:选中 A,当前状态:A=0, B=0, C=0
=== 统计结果 ===
A: 10 次
B: 2 次
C: 2 次
4.4 为什么叫"平滑"?
对比两种算法在14轮中的选中序列:
| 算法 | 选中序列(14轮) |
|---|---|
| 随机加权 | A A A A A A A B A C A A B A(可能连续7个A) |
| 平滑加权 | A A B A C A A A A B A C A A(均匀分散) |
平滑加权轮询把A的5次均匀分散到7轮中,不会连续集中在一台服务器。
这就是"平滑"的含义------流量分配更均匀,没有突发峰值。
4.5 性能优化:去掉synchronized
上面的实现用了 synchronized,高并发下会成为瓶颈。
可以优化为原子操作版本:
java
import java.util.concurrent.atomic.AtomicInteger;
/**
* 无锁平滑加权轮询(高并发优化版)
* 使用原子操作替代synchronized
*/
public class SmoothWeightedRoundRobinV2 {
private Node[] nodes;
private int totalWeight;
public SmoothWeightedRoundRobinV2(Node[] nodes) {
this.nodes = nodes;
this.totalWeight = Arrays.stream(nodes).mapToInt(n -> n.weight).sum();
}
public Node select() {
while (true) {
// 复制当前状态
int maxIndex = 0;
int maxWeight = Integer.MIN_VALUE;
for (int i = 0; i < nodes.length; i++) {
// CAS尝试增加当前权重
int current = nodes[i].currentWeight.get();
int newWeight = current + nodes[i].weight;
if (newWeight > maxWeight) {
maxWeight = newWeight;
maxIndex = i;
}
}
// 对选中的节点做CAS扣减
Node selected = nodes[maxIndex];
int current = selected.currentWeight.get();
int newWeight = current + selected.weight - totalWeight;
if (selected.currentWeight.compareAndSet(current, newWeight)) {
return selected;
}
// CAS失败则重试
}
}
static class Node {
String name;
int weight;
AtomicInteger currentWeight = new AtomicInteger(0);
public Node(String name, int weight) {
this.name = name;
this.weight = weight;
}
}
}
踩坑提醒 :实际生产中,如果服务器数量不多(<100台),
synchronized版本已经足够。过早优化是万恶之源,先保证正确性,再考虑性能。
五、Dubbo和Ribbon的加权轮询实现
5.1 Dubbo的RoundRobinLoadBalance
Dubbo的加权轮询就是基于Nginx的平滑加权轮询算法。
核心源码(简化版):
java
public class RoundRobinLoadBalance extends AbstractLoadBalance {
private static final class WeightedRoundRobin {
private int weight;
private AtomicLong current = new AtomicLong(0);
private long lastUpdate;
public void setWeight(int weight) {
this.weight = weight;
current.set(0);
}
public long increaseCurrent() {
return current.addAndGet(weight);
}
public void sel(int total) {
current.addAndGet(-1 * total);
}
}
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// 核心逻辑:每个Invoker对应一个WeightedRoundRobin
// 选中后 current -= totalWeight
// 和Nginx算法完全一致
}
}
Dubbo的特点:
- 支持权重动态调整
- 通过
lastUpdate检测长时间未更新的节点,清理缓存 - 默认预热机制:新启动的服务器权重逐渐提升到配置值
5.2 Ribbon的WeightedResponseTimeRule
Ribbon的实现思路不同------根据响应时间动态调整权重:
java
/**
* Ribbon的WeightedResponseTimeRule核心逻辑
* 响应时间越短,权重越高
*/
public class WeightedResponseTimeRule extends ClientConfigEnabledRoundRobinRule {
// 计算每个服务器的权重
void maintainWeights() {
for (Server server : serverList) {
// 获取平均响应时间
double avgResponseTime = getAvgResponseTime(server);
// 响应时间越短,权重越高
// 权重 = maxResponseTime - avgResponseTime
double weight = maxResponseTime - avgResponseTime;
serverWeights.put(server, weight);
}
}
}
Ribbon vs Dubbo:
| 特性 | Dubbo | Ribbon |
|---|---|---|
| 权重来源 | 配置(静态/动态) | 响应时间(自动计算) |
| 算法 | 平滑加权轮询 | 响应时间加权 |
| 动态调整 | 需手动配置或监听 | 自动根据RT调整 |
| 适用场景 | 已知服务器性能差异 | 性能差异不明显,需自适应 |
5.3 权重动态调整
实际生产中,权重不是一成不变的。
动态调整策略:
| 触发条件 | 调整动作 |
|---|---|
| CPU使用率 > 80% | 权重 -= 1 |
| CPU使用率 < 30% | 权重 += 1 |
| 平均响应时间 > 1s | 权重 -= 2 |
| 错误率 > 5% | 权重 = 0(下线) |
| 服务恢复后 | 权重逐渐恢复(预热) |
java
/**
* 简单的动态权重调整器
*/
public class DynamicWeightAdjuster {
public void adjustWeight(ServerMetrics metrics, Node node) {
double cpuUsage = metrics.getCpuUsage();
double avgResponseTime = metrics.getAvgResponseTime();
double errorRate = metrics.getErrorRate();
int newWeight = node.getWeight();
if (errorRate > 0.05) {
newWeight = 0; // 错误率太高,下线
} else if (cpuUsage > 0.8) {
newWeight = Math.max(1, newWeight - 1);
} else if (cpuUsage < 0.3 && avgResponseTime < 100) {
newWeight = Math.min(node.getMaxWeight(), newWeight + 1);
}
node.setWeight(newWeight);
}
}
六、踩坑指南
坑1:权重设置不合理导致某台服务器过载
我见过有人把A的权重设为100,B和C设为1。结果A扛了99%的流量,虽然A配置高,但瞬时流量峰值还是把它打垮了。权重要根据实际压测结果来设置,不是拍脑袋决定的。
坑2:服务器宕机后权重未及时调整如果B宕机了,但负载均衡器不知道,还是按原权重分配。请求发到B就会超时。必须配合健康检查,宕机的服务器权重设为0,或者从列表中移除。
坑3:权重总和为0的边界情况如果所有服务器权重都被调整为0,或者初始权重配置错误,select()方法会空转或者报错。代码里一定要有兜底处理:
javaif (totalWeight <= 0) { throw new IllegalStateException("总权重不能为0"); }
坑4:平滑加权轮询的精度问题
整数运算在极端情况下可能产生精度问题。如果权重差异很大(比如A=10000,B=1),B可能长时间选不中。这种情况下建议对权重做归一化处理,或者使用双精度浮点数版本。
七、问题与解答
Q1:平滑加权轮询和普通加权轮询有什么区别?
A:
普通加权轮询(基于随机数或简单轮询)只保证长期统计符合权重比例。
平滑加权轮询保证每个小周期内都符合权重比例,避免短时间内流量集中。
示例: A权重5,B权重1,C权重1。
- 普通轮询:可能连续7次都选中A(虽然概率低)
- 平滑轮询:7轮内一定是A选5次,B选1次,C选1次,均匀分散
Q2:权重动态调整后,平滑轮询还能保证平滑吗?
A:
权重变化后,当前轮次可能短暂不平滑,但很快会进入新的平衡。
建议在权重变化时,重置所有节点的 currentWeight 为0,让算法重新进入稳定状态。
java
public void updateWeight(String name, int newWeight) {
for (Node node : nodes) {
if (node.name.equals(name)) {
node.weight = newWeight;
}
node.currentWeight = 0; // 重置
}
this.totalWeight = nodes.stream().mapToInt(n -> n.weight).sum();
}
Q3:加权轮询和一致性哈希有什么区别?
A:
| 维度 | 加权轮询 | 一致性哈希 |
|---|---|---|
| 目的 | 按权重分配流量 | 相同的key路由到相同节点 |
| 适用场景 | 无状态服务(API网关) | 有状态服务(缓存、分片) |
| 权重支持 | 原生支持 | 通过虚拟节点支持 |
| 节点变动影响 | 所有请求重新分配 | 只影响部分key |
两者不是替代关系,而是适用场景不同。
八、面试高频考点汇总
考点1:常见的负载均衡算法有哪些?
答案:
- 随机:简单,但不均匀
- 轮询:按顺序分配,不考虑性能差异
- 加权轮询:按权重比例分配
- 平滑加权轮询:Nginx算法,均匀分散流量
- 最少连接:把请求发给当前连接数最少的服务器
- 源地址哈希:根据客户端IP哈希,保证同一客户端固定路由
- 一致性哈希:带虚拟节点,节点变动影响最小
考点2:Nginx的平滑加权轮询算法原理是什么?
答案:
每轮执行3步:
- 当前权重 += 原始权重
- 选中当前权重最大的服务器
- 选中后当前权重 -= 总权重
这样能保证权重比例正确,且流量均匀分散,不会连续集中在一台服务器。
考点3:加权轮询中,如果一台服务器宕机了怎么办?
答案:
- 配合健康检查,检测到宕机后将权重设为0
- 或者直接从服务器列表中移除
- 恢复后先给低权重,逐渐预热到正常权重
- 避免"一惊一诈":连续多次健康检查失败才判定宕机
考点4:Dubbo的负载均衡有哪些?默认是什么?
答案:
Dubbo内置4种负载均衡:
- RandomLoadBalance:随机(默认)
- RoundRobinLoadBalance:平滑加权轮询
- LeastActiveLoadBalance:最少活跃调用数
- ConsistentHashLoadBalance:一致性哈希
默认是随机,但可以通过 <dubbo:reference loadbalance="roundrobin" /> 切换。
考点5:加权轮询的权重一般怎么设置?
答案:
- 根据硬件配置:CPU核心数、内存大小
- 根据压测结果:每台服务器的实际QPS承载能力
- 动态调整:根据实时监控(CPU、内存、响应时间)自动调整
- 业务划分:核心服务给高配机器更高权重
九、模拟面试官提问和参考答案
场景题1:手写一个平滑加权轮询算法
参考答案:
java
public class SmoothWeightedRoundRobin {
private List<Node> nodes;
private int totalWeight;
public SmoothWeightedRoundRobin(List<Node> nodes) {
this.nodes = nodes;
this.totalWeight = nodes.stream().mapToInt(Node::getWeight).sum();
}
public synchronized Node select() {
Node selected = null;
int maxWeight = Integer.MIN_VALUE;
for (Node node : nodes) {
node.currentWeight += node.weight;
if (node.currentWeight > maxWeight) {
maxWeight = node.currentWeight;
selected = node;
}
}
selected.currentWeight -= totalWeight;
return selected;
}
}
关键点:currentWeight动态变化,选中后减去总权重,保证平滑。
场景题2:系统有4台服务器,权重分别是3、2、2、1,前10次请求分别发给谁?
参考答案:
总权重 = 8。
推演过程:
| 轮次 | 加权重后 | 选中 | 选中后 |
|---|---|---|---|
| 1 | 3,2,2,1 | A | -5,2,2,1 |
| 2 | -2,4,4,2 | C | -2,4,-4,2 |
| 3 | 1,6,-2,3 | B | 1,-2,-2,3 |
| 4 | 4,0,0,4 | A | -4,0,0,4 |
| 5 | -1,2,2,5 | D | -1,2,2,-3 |
| 6 | 2,4,4,-2 | C | 2,4,-4,-2 |
| 7 | 5,6,-2,-1 | B | 5,-2,-2,-1 |
| 8 | 8,0,0,0 | A | 0,0,0,0 |
| 9 | 3,2,2,1 | A | -5,2,2,1 |
| 10 | -2,4,4,2 | B | -2,-4,4,2 |
前10次:A、C、B、A、D、C、B、A、A、B
场景题3:加权轮询和最少连接算法,分别适用于什么场景?
参考答案:
-
加权轮询:适用于请求处理时间相近的场景(如REST API)。只考虑服务器性能差异,不考虑当前负载。
-
最少连接:适用于请求处理时间差异大的场景(如文件上传下载)。即使A配置高,如果A当前连接数已满,新请求也会发给B。
实际生产中可以结合:先用加权轮询预选,再过滤掉连接数过多的节点。
场景题4:如何在加权轮询中实现"慢启动"(新节点逐步接收流量)?
参考答案:
新节点上线时,不直接给全量权重,而是逐步提升:
java
public void warmUp(Node node) {
int targetWeight = node.getMaxWeight();
// 每30秒提升一次权重
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
executor.scheduleAtFixedRate(() -> {
if (node.getWeight() < targetWeight) {
node.setWeight(node.getWeight() + 1);
} else {
executor.shutdown();
}
}, 0, 30, TimeUnit.SECONDS);
}
Dubbo内置了预热逻辑,根据启动时间自动计算预热权重。
场景题5:如果服务器权重差异很大(A=100,B=1),平滑轮询会有什么问题?
参考答案:
- B选中间隔长:101轮中B只选1次,如果B在这101轮内宕机了,负载均衡器可能很久才发现
- 健康检查延迟:权重低的节点故障发现慢
- 精度问题:整数运算可能产生累积误差
解决方案:
- 权重归一化,控制最大差异(比如不超过10倍)
- 独立的健康检查机制,不依赖选中频率
- 使用浮点数版本提高精度
十、互动话题
你们项目用的负载均衡算法是什么?有没有遇到过因为权重设置不合理导致的生产事故?欢迎在评论区分享你的经历,咱们一起聊聊负载均衡的"玄学"。