前文分析了DividePlugin
插件中负载均衡的总体结构以及Random
的具体实现。今天来探索剩下两种负载均衡是如何实现的。
准备工作
Hash
找到具体实现类HashLoadBalance
直接查看
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
// 1. 遍历服务列表,为每个服务在生成 VIRTUAL_NODE_NUM 条(默认为5条)的 hash-服务 记录,放入一个有序 treemap 存放
for (DivideUpstream address : upstreamList) {
for (int i = 0; i < VIRTUAL_NODE_NUM; i++) {
long addressHash = hash("SOUL-" + address.getUpstreamUrl() + "-HASH-" + i);
treeMap.put(addressHash, address);
}
}
// 2. 使用参数 ip 生成一个 hash 值
long hash = hash(String.valueOf(ip));
// 3. 在 treemap 中找出 key 比 hash 大的所有记录
SortedMap<Long, DivideUpstream> lastRing = treeMap.tailMap(hash);
if (!lastRing.isEmpty()) {
// 4. 返回符合条件的第一条记录
return lastRing.get(lastRing.firstKey());
}
// 5. 如果没有 key 比 hash 大的记录,则返回 treemap 中的第一条记录
return treeMap.firstEntry().getValue();
}
可以看到,在 hash 算法中,并没有使用到权重值参与到计算中。
看完还有以下两个问题有点懵:
- 没有看懂这样实现负载均衡有啥优势?
- 怎么保证转发更加平均?
RoundRobin
找到RoundRobinLoadBalance
查看具体实现:
public DivideUpstream doSelect(final List<DivideUpstream> upstreamList, final String ip) {
// ...
// 1. 每次接收到请求的总权重
int totalWeight = 0;
long maxCurrent = Long.MIN_VALUE;
// ...
for (DivideUpstream upstream : upstreamList) {
// ...
// 2. 每一个服务中维护一个积累值,积累值每次增加的值为该服务的权重值
long cur = weightedRoundRobin.increaseCurrent();
// ...
// 3. 如果当前服务积累值大于 maxCurrent,则选择该服务;继续下一次循环
if (cur > maxCurrent) {
maxCurrent = cur;
selectedInvoker = upstream;
selectedWRR = weightedRoundRobin;
}
// 4. 总权重增加
totalWeight += weight;
}
// 5. 没搞懂这一部分是会在什么情况下触发;以及有什么用处?
if (!updateLock.get() && upstreamList.size() != map.size() && updateLock.compareAndSet(false, true)) {
try {
// copy -> modify -> update reference
ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<>(map);
newMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > recyclePeriod);
methodWeightMap.put(key, newMap);
} finally {
updateLock.set(false);
}
}
if (selectedInvoker != null) {
// 6. 如果选中某个服务,那么这个服务对应的 cur 减去总权重进入负值阶段,重新开始积累怒气值
selectedWRR.sel(totalWeight);
return selectedInvoker;
}
// should not happen here
return upstreamList.get(0);
}
可以看到对应于每一个服务会维护一个该服务的怒气值,RoundRobin算法会经历这两个阶段:
- 每当接收到请求后,每一个服务都会增加相当于自身权重值的怒气值。
- 最终怒气值最高的服务在这一轮胜出,执行转发请求的任务。同时,该服务的怒气值会减去总权重进入负值阶段重新开始积攒怒气值。
例如,现有权重分别为50,100的两个服务。
第一轮的 cur 依次为 50,100。100权重的服务胜出并将 cur 减去总权重。最终得到 cur 为 50,-50(100-150)
第二轮的 cur 依次为 100,50(-50+100)。50权重的服务胜出并将 cur 减去总权重。最终得到 cur 为 -50(100-150),50
第二轮的 cur 依次为 0,150。100权重的服务胜出并将 cur 减去总权重。最终得到 cur 为 0,0(150-150)。如此往复。。。
总结
Random 是每次接收请求都是独立按照各自权重进行计算;
而 RoundRobin 是把过往的每一次都算数。