1 获取权重
Dubbo中的LoadBalance,都是实现了LoadBalance接口,并继承了抽象类AbstractLoadBalance。
LoadBalance中的权重,是预热选择后的权重,通过
AbstractLoadBalance.getWeight获取。
在预热期内,权重逐步从1恢复到设置的值。
- 默认的权重值=100;
- 默认的预热时间=10分钟。
/*
如果uptime在预热时间内,则按比例降低权重;
目的流量恢复时,可以逐步的恢复。
*/
int getWeight(Invoker<?> invoker, Invocation invocation) {
// WEIGHT_KEY = "weight", DEFAULT_WEIGHT = 100
int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), WEIGHT_KEY, DEFAULT_WEIGHT);
if (weight > 0) {
long timestamp = invoker.getUrl().getParameter(TIMESTAMP_KEY, 0L);
if (timestamp > 0L) {
long uptime = System.currentTimeMillis() - timestamp;
if (uptime < 0) {
return 1;
}
// WARMUP_KEY = "warmup", DEFAULT_WARMUP = 10 * 60 * 1000 (10分钟)
int warmup = invoker.getUrl().getParameter(WARMUP_KEY, DEFAULT_WARMUP);
if (uptime > 0 && uptime < warmup) {
weight = calculateWarmupWeight((int)uptime, warmup, weight);
}
}
}
return Math.max(weight, 0);
}
// 根据uptime和warmup的比例取weight.
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
int ww = (int) ( uptime / ((float) warmup / weight));
return ww < 1 ? 1 : (Math.min(ww, weight));
}
2 RoundRobinLoadBalance
2.1 WeightedRoundRobin
WeightedRoundRobin是RoundRobin中,各invoker的权重配置对象。
protected static class WeightedRoundRobin {
// 配置的权重值,从配置中获取的固定值。
private int weight;
// 当前的权重值,随着RoundRobin过程而改变。
private AtomicLong current = new AtomicLong(0);
// 最后更新时间
private long lastUpdate;
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
current.set(0);
}
/*
原子方式执行:current = current + weight,
每次循环时调用。
*/
public long increaseCurrent() {
return current.addAndGet(weight);
}
/*
原子方式执行:current = current - total,
对每次循环中current最大的(即选中的)invoker的WeightedRoundRobin调用。
*/
public void sel(int total) {
current.addAndGet(-1 * total);
}
public long getLastUpdate() {
return lastUpdate;
}
public void setLastUpdate(long lastUpdate) {
this.lastUpdate = lastUpdate;
}
}
2.2 选择过程
确定权重的过程,就是比较和更新WeightedRoundRobin对象的过程。
每次循环,对于WeightedRoundRobin对象进行以下操作:
- 1 current = currrent + weight;
- 2 current值最大的选中的invoker;
- 3 累加totalWeight:totalWeight += weight[i]。
循环结束后:
- 4 对于选中的invoker: current = current - totalWeight。
目的: 权重越小的值,需要经过越多的循环,才能成为最大。
// WeightedRoundRobin的存储map
private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<String, ConcurrentMap<String, WeightedRoundRobin>>();
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// {group}/{interfaceName}:{version}.foo
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
// dubbo://xxxx -> WeightedRoundRobin
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
if (map == null) {
methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<String, WeightedRoundRobin>());
map = methodWeightMap.get(key);
}
int totalWeight = 0; // weight综合
long maxCurrent = Long.MIN_VALUE; // 当前的最大current,默认Long.MIN_VALUE。
long now = System.currentTimeMillis();
Invoker<T> selectedInvoker = null; // 选中的Invoker
WeightedRoundRobin selectedWRR = null; // 选中的Invoker对应的WeightedRoundRobin对象
for (Invoker<T> invoker : invokers) {
String identifyString = invoker.getUrl().toIdentityString();
WeightedRoundRobin weightedRoundRobin = map.get(identifyString);
// 从当前调用配置中,获取weight。
int weight = getWeight(invoker, invocation);
if (weightedRoundRobin == null) {
weightedRoundRobin = new WeightedRoundRobin();
weightedRoundRobin.setWeight(weight);
map.putIfAbsent(identifyString, weightedRoundRobin);
}
if (weight != weightedRoundRobin.getWeight()) {
//weight changed
// 更新weight
weightedRoundRobin.setWeight(weight);
}
// 1 对当前的WRR执行:current = current + weight,并返回current。
long cur = weightedRoundRobin.increaseCurrent();
// 设置最后更新时间
weightedRoundRobin.setLastUpdate(now);
if (cur > maxCurrent) {
// 2 选择current最大的WRR。
maxCurrent = cur;
selectedInvoker = invoker;
selectedWRR = weightedRoundRobin;
}
// 3 累加weight
totalWeight += weight;
}
// 如果invokers和map数量不相等,则更新map。
if (!updateLock.get() && invokers.size() != map.size()) {
if (updateLock.compareAndSet(false, true)) {
try {
// copy -> modify -> update reference
ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<>(map);
newMap.entrySet().removeIf(item -> now - item.getValue().getLastUpdate() > RECYCLE_PERIOD);
methodWeightMap.put(key, newMap);
} finally {
updateLock.set(false);
}
}
}
if (selectedInvoker != null) {
// 4 对选中的invoker的WRR: current = current - totalWeight
selectedWRR.sel(totalWeight);
return selectedInvoker;
}
// should not happen here
return invokers.get(0);
}
3 RandomLoadBalance
选择过程:
- 1 将各权重放入数据,下标为invokers的下标;
- 2 以totalWeight为界限产生随机数;
- 3 weights从后向前,逐步执行
offset -= weights[i],当offset<0时,则为要选择的下标。
例如 weights[] = {10, 20, 30}, 则totalWeight = 60;
如果random后:offset=40,则循环选择过程为:
第1次循环:offset = 40 - 30 = 10;
第2次循环:offset = 10 - 20 = -10 < 0,则选中20。
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
// Number of invokers
int length = invokers.size();
// Every invoker has the same weight?
boolean sameWeight = true;
// the weight of every invokers
int[] weights = new int[length];
// the first invoker's weight
int firstWeight = getWeight(invokers.get(0), invocation);
weights[0] = firstWeight;
// The sum of weights
int totalWeight = firstWeight;
for (int i = 1; i < length; i++) {
int weight = getWeight(invokers.get(i), invocation);
// save for later use
weights[i] = weight;
// Sum
totalWeight += weight;
if (sameWeight && weight != firstWeight) {
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[i];
if (offset < 0) {
return invokers.get(i);
}
}
}
// If all invokers have the same weight value or totalWeight=0, return evenly.
return invokers.get(ThreadLocalRandom.current().nextInt(length));
}
4 其它
Dubbo中的LoadBalance还有:
LeastActiveLoadBalance: 最小活跃数均衡;ConsistentHashLoadBalance: 一致性哈希均衡。
有兴趣的朋友可以深入研究一下。