ConsistentHashLoadBalance
一致性hash算法,一致性hash算法简单一点说就是一个环形,然后我们的实例服务分布在这个环上,然后基于实例服务映射多个虚拟节点。一致性hash算法为什么要加入虚拟节点呢。其实就是为了防止某一个节点突然挂掉,防止这个节点的数据全部压到顺时针的第一个节点上。导致雪崩的问题。dubbo加入虚拟节点的算法
ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
this.identityHashCode = identityHashCode;
URL url = invokers.get(0).getUrl();
this.replicaNumber = url.getMethodParameter(methodName, HASH_NODES, 160);
String[] index = COMMA_SPLIT_PATTERN.split(url.getMethodParameter(methodName, HASH_ARGUMENTS, "0"));
argumentIndex = new int[index.length];
for (int i = 0; i < index.length; i++) {
argumentIndex[i] = Integer.parseInt(index[i]);
}
// 对每个invoker走了两次for循环,然后默认是160/4,然后获取md5,然后在次hash。然后机械能添加到虚拟节点中。
for (Invoker<T> invoker : invokers) {
String address = invoker.getUrl().getAddress();
for (int i = 0; i < replicaNumber / 4; i++) {
byte[] digest = Bytes.getMD5(address + i);
for (int h = 0; h < 4; h++) {
long m = hash(digest, h);
virtualInvokers.put(m, invoker);
}
}
}
}
LeastActiveLoadBalance
会去尽量选择你的被调用最少的invoker,记录你的每个invoker当前活跃的调用有多少个。
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Number of invokers
// 获取invker的数量
int length = invokers.size();
// The least active value of all invokers
// 所有调用程序中活动最少的值
int leastActive = -1;
// The number of invokers having the same least active value (leastActive)
// 具有相同最小活动值 (minimumActive) 的调用程序数
int leastCount = 0;
// The index of invokers having the same least active value (leastActive)
// 具有相同最小活动值 (minimumActive) 的调用程序的索引
int[] leastIndexes = new int[length];
// the weight of every invokers
int[] weights = new int[length];
// The sum of the warmup weights of all the least active invokers
int totalWeight = 0;
// The weight of the first least active invoker
int firstWeight = 0;
// Every least active invoker has the same weight value?
boolean sameWeight = true;
// Filter out all the least active invokers
for (int i = 0; i < length; i++) {
Invoker<T> invoker = invokers.get(i);
// Get the active number of the invoker
// 每一次,如果你进来其以后,发现了一个invoker,他的调用此时市active。
int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive();
// Get the weight of the invoker's configuration. The default value is 100.
int afterWarmup = getWeight(invoker, invocation);
// save for later use
weights[i] = afterWarmup;
// If it is the first invoker or the active number of the invoker is less than the current least active number
// 如果说发现当前invoker的活跃调用此时是比上一个invoker的活跃调用次数是小的
// 次数会进入下面的代码
if (leastActive == -1 || active < leastActive) {
// Reset the active number of the current invoker to the least active number
leastActive = active;
// Reset the number of least active invokers
leastCount = 1;
// Put the first least active invoker first in leastIndexes
leastIndexes[0] = i;
// Reset totalWeight
totalWeight = afterWarmup;
// Record the weight the first least active invoker
firstWeight = afterWarmup;
// Each invoke has the same weight (only one invoker here)
sameWeight = true;
// If current invoker's active value equals with leaseActive, then accumulating.
} else if (active == leastActive) {
// Record the index of the least active invoker in leastIndexes order
leastIndexes[leastCount++] = i;
// Accumulate the total weight of the least active invoker
totalWeight += afterWarmup;
// If every invoker has the same weight?
if (sameWeight && afterWarmup != firstWeight) {
sameWeight = false;
}
}
}
// Choose an invoker from all the least active invokers
if (leastCount == 1) {
// If we got exactly one invoker having the least active value, return this invoker directly.
// 直接就会返回当前发现的一个活跃调用次数比上一次要小的那个invoker
return invokers.get(leastIndexes[0]);
}
if (!sameWeight && totalWeight > 0) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on
// totalWeight.
int offsetWeight = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < leastCount; i++) {
int leastIndex = leastIndexes[i];
offsetWeight -= weights[leastIndex];
if (offsetWeight < 0) {
return invokers.get(leastIndex);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
return invokers.get(leastIndexes[ThreadLocalRandom.current().nextInt(leastCount)]);
}
RandomLoadBalance
(dubbo默认的算法)随机的负载均衡。如果有权重,感觉最后亮点在最后一个for循环。
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Number of invokers
// 获取到目标服务实例集群的Invoker的数量。
int length = invokers.size();
// 判断这次随机算法是否要使用上权重
if (!needWeightLoadBalance(invokers,invocation)){
// 基于一个随机的类,nextInt,拿到invoker数量范围之内的机器对应的invoker
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
// Every invoker has the same weight?
boolean sameWeight = true;
// the maxWeight of every invokers, the minWeight = 0 or the maxWeight of the last invoker
int[] weights = new int[length];
// The sum of weights
int totalWeight = 0;
for (int i = 0; i < length; i++) {
// 去获取权重比例
int weight = getWeight(invokers.get(i), invocation);
// Sum 累加权重
totalWeight += weight;
// save for later use
// 保存每次的权重,和次数的总和。
weights[i] = totalWeight;
if (sameWeight && totalWeight != weight * (i + 1)) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
// If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
// 根据总的权重比例。然后进行随机一个数字。权重总的数据。是每个数量的相加。
int offset = ThreadLocalRandom.current().nextInt(totalWeight);
// Return a invoker based on the random value.
for (int i = 0; i < length; i++) {
// 如果offset小于了每次weights里面的数组。那么就取invokers里面的下标数据
// 权重越大,那么权重所在的下标的值范围越大。
if (offset < weights[i]) {
return invokers.get(i);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
// 如果所有调用方具有相同的权重值或 totalWeight=0,则均匀返回。
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
RoundRobinLoadBalance
轮询算法,你有10个invokers,先调用第一个invoker,在调用第二个invoker。核心的地方selectedWRR.sel(totalWeight);这个地方把第一个调用的invoker的权限进行调整。这样就可以找到下一次最高的权重的了。
protected <T> Invoker<T> doSelect(List<Invoker<T>> 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<T> selectedInvoker = null;
WeightedRoundRobin selectedWRR = null;
// 进行遍历。
for (Invoker<T> invoker : invokers) {
String identifyString = invoker.getUrl().toIdentityString();
// 获取权重的值。
int weight = getWeight(invoker, invocation);
// 再次获取判断map中是否有,没有进行添加,有久返回原来的robin
WeightedRoundRobin weightedRoundRobin = map.computeIfAbsent(identifyString, k -> {
WeightedRoundRobin wrr = new WeightedRoundRobin();
wrr.setWeight(weight);
return wrr;
});
if (weight != weightedRoundRobin.getWeight()) {
//weight changed
weightedRoundRobin.setWeight(weight);
}
long cur = weightedRoundRobin.increaseCurrent();
weightedRoundRobin.setLastUpdate(now);
// 通过这个算法,权重最高的的就是maxCurent。
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);
}
// 第一次选择的invoker。在这里就会进行权重值进行了相减的。
// 这个就是保证下一次进行轮流的轮询。
if (selectedInvoker != null) {
selectedWRR.sel(totalWeight);
return selectedInvoker;
}
// should not happen here
return invokers.get(0);
}