AbstractLoadBalance中的有五个实现类RandomLoadBalance,LeastActiveLoadBalance,RoundRobinLoadBalance,ConsistentHashLoadBalance,ShortestResponseLoadBalance
RandomLoadBalance
算法思想:
首先计算总的权重:10+20+30=60;
在0和总权重之间得到一个随机数,假设这里为26;
三个权重值可以构成3个权重区间[0,10],[11,30],[31,60],随机权重26落在[11,30]之间,所以请求就落在服务B上。
源代码:
public class RandomLoadBalance extends AbstractLoadBalance {
public static final String NAME = “random”;
/**
* 在所有的可执行者中随机选择一个
* @param invokers
* @param url
* @param invocation
* @param
* @return
/
@Override
protected Invoker doSelect(List<Invoker> invokers, URL url, Invocation invocation) {
// 可执行者的数量
int length = invokers.size();
if (!needWeightLoadBalance(invokers,invocation)){
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
// 所有可执行者的权重是否相同
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 && totalWeight != weight * (i + 1)) {
sameWeight = false;
}
}
//总权重>0并且权重不相同
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);
}
}
}
// 如果所有调用者的权重值相同或 totalWeight=0,则均匀返回。
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
}
RoundRobinLoadBalance
还是以这三个服务为例
初始权重:服务A:10,服务B:20,服务C:30
算法思想:
第一个请求的时候,服务C的权重最大,请求落在了服务c上,用服务c的权重-总权重 = 30-60=-30,此时服务A、B、C的权重分别为10,20,-30;
第二次请求的时候,他们的权重在第一次请求之后的权重基础之上加上各自的初始权重,服务A、B、C的权重分别为20,40,0;权重最大的时服务B,这是请求落在服务B上,此时服务A、B、C的权重分别为20,-20,0;
第三次请求的时候,他们的权重在第二次请求之后的权重基础之上加上各自的初始权重,服务A、B、C的权重分别为30,0,30;这时服务A和服务B的权重值一样,应该选择哪一个服务呢?轮询方式实现负载均衡,除了考虑权重之外,还要考虑预热时间uptime,选择最先完成预热的,是服务A,这是请求落在服务A上,此时服务A、B、C的权重分别为-30,0,30;
依次类推……
源代码:
public class RoundRobinLoadBalance extends AbstractLoadBalance {
@Override
protected Invoker doSelect(List<Invoker> invokers, URL url, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + “.” + invocation.getMethodName();
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.computeIfAbsent(key, k -> new ConcurrentHashMap<>());
int totalWeight = 0;
long maxCurrent = Long.MIN_VALUE;
long now = System.currentTimeMillis();
Invoker selectedInvoker = null;
WeightedRoundRobin selectedWRR = null;
for (Invoker invoker : invokers) {
String identifyString = invoker.getUrl().toIdentityString();
//当前权重
int weight = getWeight(invoker, invocation);
WeightedRoundRobin weightedRoundRobin = map.computeIfAbsent(identifyString, k -> {
WeightedRoundRobin wrr = new WeightedRoundRobin();
wrr.setWeight(weight);
return wrr;
});
if (weight != weightedRoundRobin.getWeight()) {
weightedRoundRobin.setWeight(weight);
}
//自增操作
long cur = weightedRoundRobin.increaseCurrent();
weightedRoundRobin.setLastUpdate(now);
//循环选取当前服务中最大的权重值,
if (cur > maxCurrent) {
maxCurrent = cur;
selectedInvoker = invoker;
selectedWRR = weightedRoundRobin;
}
//权重加和
totalWeight += weight;
}
if (invokers.size() != map.size()) {
map.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
}
if (selectedInvoker != null) {
//当前权重-总权重
selectedWRR.sel(totalWeight);
return selectedInvoker;
}
return invokers.get(0);
}
}
LeastActiveLoadBalance
每收到一个请求,活跃数加1,完成请求后则将活跃数减1。活跃数越小,表明该服务提供者效率越高,单位时间内可处理更多的请求。优先将请求分给活跃数小的服务提供者,当最小活跃数有多个时,根据它们的权重分配请求,权重越大,获取到新请求的概率就越大,如果权重也相同,则随机选择一个。
最小活跃数负载均衡算法的基本思想:性能好的服务提供者处理请求的速度更快,活跃数下降的也更快,活跃数小的服务提供者能够优先获取到新的服务请求
当有客户端有5个待处理请求,服务A有2个待处理请求,活跃数为2,服务B有3个待处理请求,活跃数为3。这时候再来一个请求,则优先分配给活跃数最小的服务A.
当有客户端有7个待处理请求,服务A有2个待处理请求,活跃数为2;服务B有3个待处理请求,活跃数为3;服务C有2个待处理请求,活跃数为2。这时候再来一个请求,活跃数最小的服务有A和C。
如果A和C的权重不相同,则基于权重来分配,最小活跃的权重加和10+30=40,形成[1,10],[11,40]两个区间,在[1,40]之间随机产生一个随机数,看落在那个区间就分配给哪个服务器,这里和随机负载均衡很像。
如果A和C的权重相同,则随机选择一个服务提供者
源代码:
public class LeastActiveLoadBalance extends AbstractLoadBalance {
public static final String NAME = “leastactive”;
@Override
protected Invoker doSelect(List<Invoker> 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;
// 第一个最小活跃数的权重
int firstWeight = 0;
boolean sameWeight = true;
// 找出最小活跃提供者
for (int i = 0; i < length; i++) {
Invoker invoker = invokers.get(i);
// 获取服务提供者的活跃数
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
// 获取服务提供者的权重,默认100
int afterWarmup = getWeight(invoker, invocation);
weights[i] = afterWarmup;
// 如果当前服务提供者活跃数最小
if (leastActive == -1 || active < leastActive) {
// 更新最小活跃数为当前的活跃数
leastActive = active;
// 重置最小活跃服务提供者数量为1
leastCount = 1;
// 设置当前Invoker为最小活跃Invoker的第一个值
leastIndexes[0] = i;
totalWeight = afterWarmup;
// 重置第一个最小活跃服务提供者的权重为当前服务提供者权重
firstWeight = afterWarmup;
sameWeight = true;
// 如果当前服务提供者活跃数和记录中的最小活跃数相同
} else if (active == leastActive) {
// 记录当前服务提供者到最小服务提供者下标数组
leastIndexes[leastCount++] = i;
// 总权重值加上当前的服务提供者权重
totalWeight += afterWarmup;
// 之前所有最小服务提供者的权重相同并当前权重和记录中的最小权重不同,则更新sameWeight为false
if (sameWeight && afterWarmup != firstWeight) {
sameWeight = false;
}
}
}
if (leastCount == 1) {
return invokers.get(leastIndexes[0]);
}
if (!sameWeight && totalWeight > 0) {
int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
// 根据随机值,找出对应区间的服务提供者返回
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexes[i];
offsetWeight -= weights[leastIndex];
if (offsetWeight < 0) {
return invokers.get(leastIndex);
}
}
}
// 如果所有最小活跃服务提供者权重相同或者总权重等于0,则随机返回一个服务提供者
return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}
}
ConsistentHashLoadBalance
一致性哈希工作过程:
根据 ip 或者其他的信息为缓存节点生成一个 hash,并将这个 hash 投射到 [0,4 294 967 295] 的圆环上;
当有查询或写入请求时,则为缓存项的 key 生成一个 hash 值。顺时针方向查找第一个大于或等于该 hash 值的缓存节点,并到这个节点中进行查询或写入缓存项。如果节点挂了则继续找下一个缓存节点;
将缓存节点换成Dubbo的服务提供者,相同颜色的属于同一个服务提供者,目的是通过引入虚拟节点,让 Invoker 在圆环上分散开来,避免数据倾斜问题。
一致性哈希算法比较特殊,通常用于缓存场景。
源代码:
public class ConsistentHashLoadBalance extends AbstractLoadBalance {
@Override
protected Invoker doSelect(List<Invoker> invokers, URL url, Invocation invocation) {
String methodName = RpcUtils.getMethodName(invocation);
String key = invokers.get(0).getUrl().getServiceKey() + “.” + methodName;
int invokersHashCode = invokers.hashCode();
ConsistentHashSelector selector = (ConsistentHashSelector) selectors.get(key);
if (selector == null || selector.identityHashCode != invokersHashCode) {
selectors.put(key, new ConsistentHashSelector(invokers, methodName, invokersHashCode));
selector = (ConsistentHashSelector) selectors.get(key);
}
return selector.select(invocation);
}
}
ShortestResponseLoadBalance
最短响应时间的算法思想和最小活跃数差不多,不同的是添加了一个历史平均响应时间,历史平均响应时间 = 历史响应成功的时间总和/历史响应成功的次数,根据历史平均响应时间预估选出最小值
源代码:
public class ShortestResponseLoadBalance extends AbstractLoadBalance {
public static final String NAME = “shortestresponse”;
@Override
protected Invoker doSelect(List<Invoker> invokers, URL url, Invocation invocation) {
// 服务提供者数量
int length = invokers.size();
// 估计所有调用者的最短响应时间
long shortestResponse = Long.MAX_VALUE;
// 具有相同估计最短响应时间的调用者数量
int shortestCount = 0;
// 具有相同估计最短响应时间的调用者的索引
int[] shortestIndexes = new int[length];
// 每个服务调用者的权重
int[] weights = new int[length];
// 所有最短响应调用者的预热权重之和
int totalWeight = 0;
// 第一个最短响应调用者的权重
int firstWeight = 0;
// 每个最短的响应调用者都有相同的权重值?
boolean sameWeight = true;
// 找出最短响应时间服务提供者
for (int i = 0; i < length; i++) {
Invoker invoker = invokers.get(i);
RpcStatus rpcStatus = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
// 根据活动连接和成功平均经过时间的乘积计算历史平均响应时间
long succeededAverageElapsed = rpcStatus.getSucceededAverageElapsed();
//获取服务提供者活跃数
int active = rpcStatus.getActive();
long estimateResponse = succeededAverageElapsed * active;
int afterWarmup = getWeight(invoker, invocation);
weights[i] = afterWarmup;
// 估计的响应时间 < 最短响应时间
if (estimateResponse < shortestResponse) {
shortestResponse = estimateResponse;
shortestCount = 1;
shortestIndexes[0] = i;
totalWeight = afterWarmup;
firstWeight = afterWarmup;
sameWeight = true;
//估计的响应时间 = 最短响应时间
} else if (estimateResponse == shortestResponse) {
//记录当前服务提供者到最短相应时间服务提供者下标
shortestIndexes[shortestCount++] = i;
totalWeight += afterWarmup;
if (sameWeight && i > 0
&& afterWarmup != firstWeight) {
sameWeight = false;
}
}
}
if (shortestCount == 1) {
//如果最短响应时间服务提供者只有一个时,直接返回
return invokers.get(shortestIndexes[0]);
}
//所有服务提供的权重不相同,并且权重总和 > 0,在[1,权重总和]范围内随机选自一个随机数
if (!sameWeight && totalWeight > 0) {
int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
//根据随机值,找出对应区间的服务提供者返回
for (int i = 0; i < shortestCount; i++) {
int shortestIndex = shortestIndexes[i];
offsetWeight -= weights[shortestIndex];
if (offsetWeight < 0) {
return invokers.get(shortestIndex);
}
}
}
//如果所有最短响应时间服务提供者权重相同或者总权重等于0,则随机返回一个服务提供者
return invokers.get(shortestIndexes[ThreadLocalRandom.current().nextInt(shortestCount)]);
}
}