核心概念
负载均衡是指在多个服务提供者(Provider)之间分配请求的策略。Dubbo 在 Consumer 端实现负载均衡,由客户端根据策略选择一个 Provider 发起调用,避免了中心化代理的性能瓶颈。
核心特点:
- 客户端负载均衡:在 Consumer 端选择 Provider
- 多种策略:支持随机、轮询、最少活跃数等
- 可扩展:基于 SPI 机制,可自定义策略
- 动态权重:支持服务提供者权重配置
Dubbo 支持的负载均衡策略
1. Random(随机,默认)
原理:
- 按权重随机选择 Provider
- 权重相同时,完全随机
- 权重不同时,权重越大被选中概率越高
实现代码:
public class RandomLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, Invocation invocation) {
int length = invokers.size();
// 1. 计算总权重
int totalWeight = 0;
boolean sameWeight = true;
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
totalWeight += weight;
if (sameWeight && i > 0 && weight != getWeight(invokers.get(i - 1), invocation)) {
sameWeight = false;
}
}
// 2. 如果权重不同,按权重随机
if (totalWeight > 0 && !sameWeight) {
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
for (Invoker<T> invoker : invokers) {
offset -= getWeight(invoker, invocation);
if (offset < 0) {
return invoker;
}
}
}
// 3. 权重相同,直接随机
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
配置方式:
// 服务级别配置
@Service(loadbalance = "random")
public class UserServiceImpl implements UserService {
// ...
}
// 方法级别配置
@Service
public class UserServiceImpl implements UserService {
@Method(name = "getUser", loadbalance = "random")
public User getUser(Long userId) {
// ...
}
}
// Consumer 端配置(优先级更高)
@Reference(loadbalance = "random")
private UserService userService;
权重配置:
// Provider 端配置权重
@Service(weight = 200) // 默认权重 100
public class UserServiceImpl implements UserService {
// ...
}
// 动态配置权重(Dubbo Admin)
// dubbo.registry.address=zookeeper://127.0.0.1:2181
// 服务A:权重 200(处理能力强)
// 服务B:权重 100(处理能力一般)
// 服务C:权重 50(处理能力弱)
适用场景:
- ✅ 默认选择,适合大多数场景
- ✅ 服务提供者性能差异大(通过权重调整)
- ✅ 无状态服务
- ❌ 需要严格均匀分配的场景
示例:
// 假设有 3 个 Provider
// Provider A: 权重 100
// Provider B: 权重 200
// Provider C: 权重 100
//
// 总权重 = 400
// 随机数 offset = random(0, 400)
//
// offset ∈ [0, 100) → 选择 A
// offset ∈ [100, 300) → 选择 B
// offset ∈ [300, 400) → 选择 C
//
// 结果:B 的调用量是 A 和 C 的 2 倍
2. RoundRobin(轮询)
原理:
- 按权重轮询
- 每个 Provider 依次被调用
- 支持权重,权重越大被调用次数越多
实现代码:
public class RoundRobinLoadBalance extends AbstractLoadBalance {
// 每个服务的调用计数器
private final ConcurrentMap<String, AtomicPositiveInteger> sequences =
new ConcurrentHashMap<>();
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
int length = invokers.size();
int maxWeight = 0;
int minWeight = Integer.MAX_VALUE;
// 1. 计算最大、最小权重
for (Invoker<T> invoker : invokers) {
int weight = getWeight(invoker, invocation);
maxWeight = Math.max(maxWeight, weight);
minWeight = Math.min(minWeight, weight);
}
// 2. 如果权重不同,使用加权轮询
if (maxWeight > 0 && minWeight < maxWeight) {
AtomicPositiveInteger sequence = sequences.computeIfAbsent(key,
k -> new AtomicPositiveInteger());
int currentWeight = sequence.getAndIncrement() % (maxWeight * length);
for (int i = 0; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
if (currentWeight < weight) {
return invokers.get(i);
}
currentWeight -= weight;
}
}
// 3. 权重相同,简单轮询
AtomicPositiveInteger sequence = sequences.computeIfAbsent(key,
k -> new AtomicPositiveInteger());
return invokers.get(sequence.getAndIncrement() % length);
}
}
配置方式:
@Service(loadbalance = "roundrobin")
public class UserServiceImpl implements UserService {
// ...
}
@Reference(loadbalance = "roundrobin")
private UserService userService;
适用场景:
- ✅ 需要均匀分配请求
- ✅ 每个请求的处理时间相近
- ✅ 服务提供者性能一致
- ❌ 请求处理时间差异大(可能导致某些 Provider 积压)
示例:
// 假设有 3 个 Provider(权重相同)
// Provider A、B、C
//
// 调用顺序:A → B → C → A → B → C → ...
//
// 假设权重不同:
// Provider A: 权重 100
// Provider B: 权重 200
// Provider C: 权重 100
//
// 调用顺序:A → B → B → C → A → B → B → C → ...
// 结果:B 的调用次数是 A 和 C 的 2 倍
3. LeastActive(最少活跃数,推荐)
原理:
- 选择活跃调用数最少的 Provider
- 活跃数:正在处理的请求数量
- 慢的 Provider 收到更少请求,快的 Provider 收到更多请求
- 自动负载均衡,适应不同性能的 Provider
实现代码:
public class LeastActiveLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, Invocation invocation) {
int length = invokers.size();
// 1. 找出最少活跃数
int leastActive = -1;
int leastCount = 0; // 最少活跃数的 Provider 数量
int[] leastIndexes = new int[length];
int totalWeight = 0;
int firstWeight = 0;
boolean sameWeight = true;
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;
firstWeight = weight;
sameWeight = true;
} else if (active == leastActive) {
leastIndexes[leastCount++] = i;
totalWeight += weight;
if (sameWeight && weight != firstWeight) {
sameWeight = false;
}
}
}
// 2. 如果只有一个最少活跃数的 Provider,直接返回
if (leastCount == 1) {
return invokers.get(leastIndexes[0]);
}
// 3. 有多个最少活跃数的 Provider,按权重随机
if (!sameWeight && totalWeight > 0) {
int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexes[i];
offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
if (offsetWeight < 0) {
return invokers.get(leastIndex);
}
}
}
// 4. 权重相同,随机选择
return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}
}
配置方式:
@Service(loadbalance = "leastactive")
public class UserServiceImpl implements UserService {
// ...
}
@Reference(loadbalance = "leastactive")
private UserService userService;
适用场景:
- ✅ 推荐使用,智能负载均衡
- ✅ Provider 性能差异大
- ✅ 请求处理时间差异大
- ✅ 自动避开慢节点
- ✅ 异构集群(不同配置的服务器)
示例:
// 假设有 3 个 Provider
// Provider A: 当前活跃数 10(慢)
// Provider B: 当前活跃数 2(快)
// Provider C: 当前活跃数 5(中)
//
// 选择:Provider B(活跃数最少)
//
// 优势:
// - 慢的 Provider 自动收到更少请求
// - 快的 Provider 自动承担更多流量
// - 无需手动配置权重
4. ConsistentHash(一致性哈希)
原理:
- 根据请求参数进行哈希,相同参数总是路由到同一个 Provider
- 适合有状态服务(如缓存、会话)
- 基于一致性哈希环,节点变化时影响范围小
实现代码:
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, Invocation invocation) {
String methodName = invocation.getMethodName();
String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;
// 获取或创建一致性哈希选择器
ConsistentHashSelector<T> selector = selectors.get(key);
if (selector == null || selector.getInvokerHashCode() != invokers.hashCode()) {
selector = new ConsistentHashSelector<>(invokers, methodName, invocation);
selectors.put(key, selector);
}
return selector.select(invocation);
}
private static class ConsistentHashSelector<T> {
// 虚拟节点(160 个虚拟节点)
private final TreeMap<Long, Invoker<T>> virtualInvokers;
ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, Invocation invocation) {
virtualInvokers = new TreeMap<>();
// 为每个 Provider 创建虚拟节点
for (Invoker<T> invoker : invokers) {
String address = invoker.getUrl().getAddress();
// 每个 Provider 创建 160 个虚拟节点
for (int i = 0; i < 160; i++) {
byte[] digest = md5(address + i);
for (int j = 0; j < 4; j++) {
long hash = hash(digest, j);
virtualInvokers.put(hash, invoker);
}
}
}
}
public Invoker<T> select(Invocation invocation) {
// 根据请求参数计算哈希值
String key = toKey(invocation.getArguments());
byte[] digest = md5(key);
long hash = hash(digest, 0);
// 在哈希环上找到最近的 Provider
Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);
if (entry == null) {
entry = virtualInvokers.firstEntry();
}
return entry.getValue();
}
}
}
配置方式:
// 默认根据第一个参数进行哈希
@Service(loadbalance = "consistenthash")
public class UserServiceImpl implements UserService {
// ...
}
// 指定哈希参数位置(多个参数)
@Service(loadbalance = "consistenthash",
parameters = {"hash.arguments", "0,1"}) // 根据第1、2个参数哈希
public class UserServiceImpl implements UserService {
User getUser(Long userId, String type);
}
// 指定虚拟节点数
@Service(loadbalance = "consistenthash",
parameters = {"hash.nodes", "320"}) // 320 个虚拟节点
public class UserServiceImpl implements UserService {
// ...
}
适用场景:
- ✅ 有状态服务(缓存、会话保持)
- ✅ 需要请求亲和性(相同参数路由到同一节点)
- ✅ 分布式缓存场景
- ❌ 无状态服务(推荐用其他策略)
示例:
// 缓存场景
@Service(loadbalance = "consistenthash")
public class CacheServiceImpl implements CacheService {
@Override
public String get(String key) {
// 相同的 key 总是路由到同一个 Provider
// 提高缓存命中率
return cache.get(key);
}
}
// 调用示例
cacheService.get("user:123"); // 路由到 Provider A
cacheService.get("user:123"); // 路由到 Provider A(相同节点)
cacheService.get("user:456"); // 可能路由到 Provider B
5. ShortestResponse(最短响应时间,Dubbo 3.0+)
原理:
- 选择平均响应时间最短的 Provider
- 综合考虑活跃数和响应时间
- 比 LeastActive 更智能
配置方式:
@Service(loadbalance = "shortestresponse")
public class UserServiceImpl implements UserService {
// ...
}
适用场景:
- ✅ Dubbo 3.0+ 推荐使用
- ✅ 替代 LeastActive
- ✅ 更精准的负载均衡
负载均衡策略对比
| 策略 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Random | 随机 + 权重 | 简单,性能好 | 可能不均匀 | 默认选择 |
| RoundRobin | 轮询 + 权重 | 分配均匀 | 慢节点可能积压 | 性能一致的集群 |
| LeastActive | 最少活跃数 | 自动避开慢节点 | 稍复杂 | 推荐,异构集群 |
| ConsistentHash | 一致性哈希 | 请求亲和性 | 不均匀 | 有状态服务 |
| ShortestResponse | 最短响应时间 | 最智能 | Dubbo 3.0+ | Dubbo 3.0+ |
自定义负载均衡策略
// 1. 实现 LoadBalance 接口
@SPI
public class MyLoadBalance extends AbstractLoadBalance {
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, Invocation invocation) {
// 自定义选择逻辑
// 例如:根据地理位置、机房就近选择
String clientRegion = RpcContext.getContext().getAttachment("region");
for (Invoker<T> invoker : invokers) {
String providerRegion = invoker.getUrl().getParameter("region");
if (clientRegion.equals(providerRegion)) {
return invoker;
}
}
// 没有同地域的,随机选择
return invokers.get(ThreadLocalRandom.current().nextInt(invokers.size()));
}
}
// 2. 配置 SPI
// META-INF/dubbo/org.apache.dubbo.rpc.cluster.LoadBalance
// myloadbalance=com.example.MyLoadBalance
// 3. 使用自定义策略
@Service(loadbalance = "myloadbalance")
public class UserServiceImpl implements UserService {
// ...
}
最佳实践
1. 根据场景选择策略
// 常规业务:Random(默认)
@Service(loadbalance = "random")
public class OrderServiceImpl { }
// 异构集群:LeastActive
@Service(loadbalance = "leastactive")
public class PaymentServiceImpl { }
// 缓存服务:ConsistentHash
@Service(loadbalance = "consistenthash")
public class CacheServiceImpl { }
2. 配置权重
// Provider 端配置权重
@Service(weight = 200) // 高性能服务器
public class UserServiceImpl { }
@Service(weight = 100) // 普通服务器
public class UserServiceImpl { }
// 动态调整权重(Dubbo Admin)
// 实时调整,无需重启
3. Consumer 端覆盖
// Consumer 端可以覆盖 Provider 的配置
@Reference(
loadbalance = "leastactive", // 覆盖 Provider 的配置
retries = 2 // 失败重试
)
private UserService userService;
4. 方法级别配置
@Service
public class UserServiceImpl implements UserService {
// 读操作:轮询
@Method(name = "getUser", loadbalance = "roundrobin")
public User getUser(Long userId) { }
// 写操作:最少活跃数
@Method(name = "updateUser", loadbalance = "leastactive")
public void updateUser(User user) { }
}
答题总结
Dubbo 支持的负载均衡策略及选择建议:
1. 核心策略(按推荐度):
- Random:随机 + 权重,默认策略 ⭐⭐⭐⭐
- LeastActive:最少活跃数,推荐使用 ⭐⭐⭐⭐⭐
- RoundRobin:轮询 + 权重 ⭐⭐⭐⭐
- ConsistentHash:一致性哈希,有状态服务 ⭐⭐⭐⭐
- ShortestResponse:最短响应时间(Dubbo 3.0+) ⭐⭐⭐⭐⭐
2. 选择建议:
- 默认/通用 → Random
- 异构集群/智能 → LeastActive
- 均匀分配 → RoundRobin
- 有状态/缓存 → ConsistentHash
- Dubbo 3.0+ → ShortestResponse
3. 核心机制:
- 客户端负载均衡:在 Consumer 端选择 Provider
- 权重支持:可配置不同 Provider 的权重
- 活跃数统计:实时统计每个 Provider 的活跃请求数
- 可扩展:基于 SPI,可自定义策略
4. 配置方式:
@Service(loadbalance = "leastactive", weight = 200)
@Reference(loadbalance = "random")
面试技巧:强调 Random 是默认策略,LeastActive 更智能(自动避开慢节点),ConsistentHash 适合有状态服务。可以画出负载均衡的选择流程图,体现对分布式系统的理解。