负载均衡策略详解
负载均衡策略详解
Section titled “负载均衡策略详解”负载均衡概述
Section titled “负载均衡概述”什么是负载均衡?
Section titled “什么是负载均衡?” ┌─────────────┐ │ Client │ └──────┬──────┘ │ ┌──────────────┼──────────────┐ │ │ │ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │Server 1 │ │Server 2 │ │Server 3 │ │ 30% │ │ 30% │ │ 40% │ └─────────┘ └─────────┘ └─────────┘负载均衡的目标:
- 合理分配请求,避免单点过载
- 提高系统吞吐量
- 提高系统可用性
Dubbo 负载均衡策略
Section titled “Dubbo 负载均衡策略”1. Random LoadBalance(随机)
Section titled “1. Random LoadBalance(随机)”原理:随机选择一个可用服务器
// 伪代码实现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" />2. RoundRobin LoadBalance(轮询)
Section titled “2. RoundRobin LoadBalance(轮询)”原理:依次选择可用服务器
// 伪代码实现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") = 100100 落在 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 |
面试高频问题
Section titled “面试高频问题”Q1: 一致性 Hash 算法的原理是什么?
Section titled “Q1: 一致性 Hash 算法的原理是什么?”参考答案:
- 将服务器和请求都 Hash 到一个环上
- 相同参数的请求总是路由到同一服务器
- 使用虚拟节点解决数据倾斜问题
- 服务器增减时,只影响相邻节点
Q2: 如何选择负载均衡策略?
Section titled “Q2: 如何选择负载均衡策略?”参考答案:
- 性能相近的服务器:Random、RoundRobin
- 性能差异大:LeastActive
- 需要缓存路由:ConsistentHash
- 实时性要求高:LeastActive
Q3: LeastActive 是如何工作的?
Section titled “Q3: LeastActive 是如何工作的?”参考答案:
- 统计每个服务器当前活跃请求数
- 选择活跃数最少的服务器
- 如果多个服务器活跃数相同,随机选择
- 可以结合权重分配