Skip to content

负载均衡策略详解


┌─────────────┐
│ Client │
└──────┬──────┘
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Server 1 │ │Server 2 │ │Server 3 │
│ 30% │ │ 30% │ │ 40% │
└─────────┘ └─────────┘ └─────────┘

负载均衡的目标:

  • 合理分配请求,避免单点过载
  • 提高系统吞吐量
  • 提高系统可用性

原理:随机选择一个可用服务器

// 伪代码实现
public class RandomLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size();
boolean sameWeight = true;
int[] weights = new int[length];
int totalWeight = 0;
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
totalWeight += weight;
weights[i] = totalWeight;
if (sameWeight && i > 0 && weight != weights[i - 1]) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0; i < length; i++) {
if (offset < weights[i]) {
return invokers.get(i);
}
}
}
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}

特点

  • 权重相同时:每个服务器概率相等
  • 权重不同时:按权重分配概率
  • 适用场景:大多数情况

配置

<dubbo:reference loadbalance="random" />

原理:依次选择可用服务器

// 伪代码实现
public class RoundRobinLoadBalance extends AbstractLoadBalance {
private Map<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<>();
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey();
int length = invokers.size();
// 获取当前计数器
AtomicPositiveInteger sequence = sequences.computeIfAbsent(
key, k -> new AtomicPositiveInteger()
);
// 轮询选择
int positive = sequence.getAndIncrement();
int index = positive % length;
return invokers.get(index);
}
}

特点

  • 请求均匀分配
  • 不考虑服务器性能差异
  • 改进:加权轮询

加权轮询配置

<!-- provider 端配置权重 -->
<dubbo:service weight="100" />
<!-- consumer 端指定负载均衡策略 -->
<dubbo:reference loadbalance="roundrobin" />

3. LeastActive LoadBalance(最少活跃数)

Section titled “3. LeastActive LoadBalance(最少活跃数)”

原理:选择活跃数最少的服务器

// 伪代码实现
public class LeastActiveLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size();
int leastActive = -1;
int leastCount = 0;
int[] leastIndexes = new int[length];
int[] weights = new int[length];
int totalWeight = 0;
// 找到最少活跃的服务器
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
int weight = getWeight(invoker, invocation);
if (leastActive == -1 || active < leastActive) {
leastActive = active;
leastCount = 1;
leastIndexes[0] = i;
totalWeight = weight;
} else if (active == leastActive) {
leastIndexes[leastCount++] = i;
totalWeight += weight;
}
weights[i] = weight;
}
// 随机选择一个最少活跃的服务器
if (leastCount == 1) {
return invokers.get(leastIndexes[0]);
}
if (totalWeight > 0) {
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0; i < leastCount; i++) {
offset -= weights[leastIndexes[i]];
if (offset < 0) {
return invokers.get(leastIndexes[i]);
}
}
}
return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}
}

特点

  • 动态感知服务器负载
  • 性能好的服务器处理更快,优先分配
  • 适用于服务器性能不均的场景

4. ConsistentHash LoadBalance(一致性 Hash)

Section titled “4. ConsistentHash LoadBalance(一致性 Hash)”

原理:相同参数的请求路由到同一服务器

原始 Hash 环:
┌──────────────────────────────────────┐
│ │
│ Server A ───> Server B │
│ ▲ │ │
│ │ ▼ │
│ └──────── Server C │
│ │
└──────────────────────────────────────┘
请求 Hash:hash("userId:1001") = 100
100 落在 Server B 区间 → 路由到 Server B
// 伪代码实现
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
private final ConcurrentHashMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<>();
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
String key = url.getServiceKey();
int identityHashCode = System.identityHashCode(invokers);
ConsistentHashSelector<T> selector = selectors.get(key);
if (selector == null || selector.identityHashCode != identityHashCode) {
selectors.put(key, new ConsistentHashSelector<>(invokers, identityHashCode));
selector = selectors.get(key);
}
return selector.select(invocation);
}
static class ConsistentHashSelector<T> {
private final TreeMap<Long, Invoker<T>> virtualInvokers = new TreeMap<>();
private final int replicaNumber = 160; // 虚拟节点数
ConsistentHashSelector(List<Invoker<T>> invokers, int identityHashCode) {
for (Invoker<T> invoker : invokers) {
for (int i = 0; i < replicaNumber / 4; i++) {
// 为每个服务器创建 40 个虚拟节点
long hash = hash(invoker.getUrl().toString() + i);
virtualInvokers.put(hash, invoker);
}
}
}
Invoker<T> select(Invocation invocation) {
String key = getKey(invocation);
long hash = hash(key);
// 找到第一个大于等于 hash 的虚拟节点
Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);
// 如果没找到,从头开始
if (entry == null) {
entry = virtualInvokers.firstEntry();
}
return entry.getValue();
}
}
}

特点

  • 相同参数总是路由到同一服务器
  • 解决分布式缓存问题
  • 虚拟节点解决数据倾斜

配置

<!-- 使用一致性 Hash,参数名用 | 分隔 -->
<dubbo:reference loadbalance="consistenthash" />
<!-- 或指定参数 -->
<dubbo:method name="getUser" loadbalance="consistenthash" />

策略优点缺点适用场景
Random简单、公平无法感知负载大多数场景
RoundRobin均匀不考虑性能差异性能相近的服务器
LeastActive动态感知负载需要统计活跃数性能差异大
ConsistentHash缓存友好扩容时rehash缓存、Session

Q1: 一致性 Hash 算法的原理是什么?

Section titled “Q1: 一致性 Hash 算法的原理是什么?”

参考答案

  1. 将服务器和请求都 Hash 到一个环上
  2. 相同参数的请求总是路由到同一服务器
  3. 使用虚拟节点解决数据倾斜问题
  4. 服务器增减时,只影响相邻节点

参考答案

  • 性能相近的服务器:Random、RoundRobin
  • 性能差异大:LeastActive
  • 需要缓存路由:ConsistentHash
  • 实时性要求高:LeastActive

参考答案

  1. 统计每个服务器当前活跃请求数
  2. 选择活跃数最少的服务器
  3. 如果多个服务器活跃数相同,随机选择
  4. 可以结合权重分配