本文基于dubbo v2.6.x
本篇我们将要分析dubbo负载均衡RoundRobinLoadBalance的源码,RoundRobinLoadBalance主要是以轮询的方式来选取invoker,我们直接看下源码:
public class RoundRobinLoadBalance extends AbstractLoadBalance {
public static final String NAME = "roundrobin";
private static int RECYCLE_PERIOD = 60000;
protected static class WeightedRoundRobin {
private int weight;// 权重值
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);// 设置成0
}
// 将权重值设置到atomiclong中
public long increaseCurrent() {
return current.addAndGet(weight);
}
public void sel(int total) {
current.addAndGet(-1 * total);
}
public long getLastUpdate() {
return lastUpdate;
}
public void setLastUpdate(long lastUpdate) {
this.lastUpdate = lastUpdate;
}
}
// 缓存 key =接口全类名.方法名 value = map<每个invoker的标识,WeightedRoundRobin >
private ConcurrentMap<String, ConcurrentMap<String, WeightedRoundRobin>> methodWeightMap = new ConcurrentHashMap<String, ConcurrentMap<String, WeightedRoundRobin>>();
private AtomicBoolean updateLock = new AtomicBoolean();
/**
* get invoker addr list cached for specified invocation
* <p>
* <b>for unit test only</b>
*
* @param invokers
* @param invocation
* @return
*/
protected <T> Collection<String> getInvokerAddrList(List<Invoker<T>> invokers, Invocation invocation) {
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
Map<String, WeightedRoundRobin> map = methodWeightMap.get(key);
if (map != null) {
return map.keySet();
}
return null;
}
// 进行选择
@Override
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
/// 组装key = 接口全类名.方法名
String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName();
// 根据key 从缓存中获取
ConcurrentMap<String, WeightedRoundRobin> map = methodWeightMap.get(key);
// 处理是空的情况,如果为空就创建 然后塞进去
if (map == null) {
methodWeightMap.putIfAbsent(key, new ConcurrentHashMap<String, WeightedRoundRobin>());
map = methodWeightMap.get(key);
}
int totalWeight = 0;
//初始化max 为long最小值
long maxCurrent = Long.MIN_VALUE;
// 获取系统当前时间
long now = System.currentTimeMillis();
Invoker<T> selectedInvoker = null;
WeightedRoundRobin selectedWRR = null;
// 遍历invokers
for (Invoker<T> invoker : invokers) {
// 将url 转成 身份标识
String identifyString = invoker.getUrl().toIdentityString();
// 从map中获取
WeightedRoundRobin weightedRoundRobin = map.get(identifyString);
// 计算权重
int weight = getWeight(invoker, invocation);
if (weight < 0) {// 如果计算的权重小于0 , 设置成0
weight = 0;
}
if (weightedRoundRobin == null) {// 没有获取到,说明没有缓存
weightedRoundRobin = new WeightedRoundRobin();//创建WeightedRoundRobin对象
weightedRoundRobin.setWeight(weight);// 设置权重
// 将WeightedRoundRobin 对象塞到 map中
map.putIfAbsent(identifyString, weightedRoundRobin);
weightedRoundRobin = map.get(identifyString);
}
//如果当前计算的权重与之前缓存的比匹配就设置成当前权重
if (weight != weightedRoundRobin.getWeight()) {
//weight changed
weightedRoundRobin.setWeight(weight);
}
// 将权重值设置到atomiclong 中
long cur = weightedRoundRobin.increaseCurrent();
// 设置最后修改时间
weightedRoundRobin.setLastUpdate(now);
// 如果当前权重 > max
if (cur > maxCurrent) {
maxCurrent = cur;// max 设置成赋值为当前权重
selectedInvoker = invoker;
selectedWRR = weightedRoundRobin;
}
// 总权重+ 当前权重
totalWeight += weight;
}
// 没有在修改的 && 当前所有的invoker 列表大小 不等于 缓存里面的大小
if (!updateLock.get() && invokers.size() != map.size()) {
if (updateLock.compareAndSet(false, true)) {// cas 操作
try {
// copy -> modify -> update reference
ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<String, WeightedRoundRobin>();
newMap.putAll(map);// 将map添加到 newMap中
Iterator<Entry<String, WeightedRoundRobin>> it = newMap.entrySet().iterator();
while (it.hasNext()) {
Entry<String, WeightedRoundRobin> item = it.next();
// 移除 1分钟没有改动过的
if (now - item.getValue().getLastUpdate() > RECYCLE_PERIOD) {
it.remove();
}
}// 替换成新的
methodWeightMap.put(key, newMap);
} finally {
updateLock.set(false);
}
}
}
if (selectedInvoker != null) {
// 将选中的那个weightedRoundRobin 里面的current设置成负的 totalWeight
selectedWRR.sel(totalWeight);
return selectedInvoker;// 返回选中的那个invoker
}
/// 最后就是选择第一个
// should not happen here
return invokers.get(0);
}
}
RoundRobinLoadBalance也是继承AbstractLoadBalance实现doSelect方法。我们先看doSelect的实现,首先是 生成key =接口全类名.方法名,去缓存methodWeightMap 中获取key对应的值,这个methodWeightMap 是个map,然后key就是咱们上面说的那个,value是 一个map (key是服务提供这url的信息,value是WeightedRoundRobin 对象,这对象我们稍后再看),这里没有key对应的value的话,就会new ConcurrentHashMap<String, WeightedRoundRobin>()塞进去。
接着就是遍历invokers,将invoker里面url转成 一个标示identifyString,以这个identifyString 为key从上面获取的那个map中获取对应的WeightedRoundRobin对象。计算该invoker权重,如果是小于0的设置为0。如果获取的WeightedRoundRobin对象为空的话,创建这个对象,将invoker对应的权重放到这个对象中,将这个对象缓存到map中,如果当前的权重与之前WeightedRoundRobin对象中的权重不相等,就设置到这个对象中。long cur = weightedRoundRobin.increaseCurrent();
这句其实就是将权重塞到AtomicLong对象中,设置对象的最后更新时间。
接下来这段代码,其实就是找最大权重的 invoker跟对应的weightedRoundRobin,maxCurrent初始值是long的最小值,然后当前这个权重大于maxCurrent 的时候,赋值给maxCurrent ,同时selectedInvoker 就是那个invoker,selectedWRR 就是对应的weightedRoundRobin,这样子一圈下来,最后这个selectedInvoker 就是最大权重那个。
// 如果当前权重 > max
if (cur > maxCurrent) {
maxCurrent = cur;// max 设置成赋值为当前权重
selectedInvoker = invoker;
selectedWRR = weightedRoundRobin;
}
再往下走 ,其实就是当 invoker列表大小与 map大小不一致的时候,这个不一致,一般就是有服务提供者下线的时候会出现,这里其实就是移除那些超过1分钟没有被更新过的(其实就是1分钟invokers列表没有出现过的)缓存,这里是先new 了个map,然后将之前的塞进去,遍历新的,移除那些掉线的服务,最后将新的map赋值给来的map。
// 没有在修改的 && 当前所有的invoker 列表大小 不等于 缓存里面的大小(说明还有没缓存进去的)
if (!updateLock.get() && invokers.size() != map.size()) {
if (updateLock.compareAndSet(false, true)) {// cas 操作
try {
// copy -> modify -> update reference
ConcurrentMap<String, WeightedRoundRobin> newMap = new ConcurrentHashMap<String, WeightedRoundRobin>();
newMap.putAll(map);// 将map添加到 newMap中
Iterator<Entry<String, WeightedRoundRobin>> it = newMap.entrySet().iterator();
while (it.hasNext()) {
Entry<String, WeightedRoundRobin> item = it.next();
// 移除 1分钟没有改动过的
if (now - item.getValue().getLastUpdate() > RECYCLE_PERIOD) {
it.remove();
}
}// 替换成新的
methodWeightMap.put(key, newMap);
} finally {
updateLock.set(false);
}
}
}
最后,如果选中的那个invoker 不是空,将选中的那个weightedRoundRobin 里面的current设置成负的 totalWeight,这个totalWeight 就是所有权重的和,这样到下一轮就轮不到这个invoker了,所有都被选中一次的话都变成负的了,再从负的里面找最大的,所有又被选中一次,然后都变成了正数,就是这样轮询的 。最后实在没有获取到的话就取第一个invoker。
if (selectedInvoker != null) {
// 将选中的那个weightedRoundRobin 里面的current设置成负的 totalWeight
selectedWRR.sel(totalWeight);
return selectedInvoker;// 返回选中的那个invoker
}
/// 最后就是选择第一个
// should not happen here
return invokers.get(0);