08 dubbo源码学习_LoadBalance

1. loadBalance的作用

它的职责是将网络请求,或者其他形式的负载“均摊”到不同的机器上。避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况。通过负载均衡,可以让每台服务器获取到适合自己处理能力的负载

2. loadBalance的入口

入口是在AbstractClusterInvoker中,这个抽象类要上一篇中已经讲过,它有一个方法:

private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
    if (invokers == null || invokers.isEmpty())
        return null;
    if (invokers.size() == 1)
        return invokers.get(0);
    // SPI选择loadbalance
    if (loadbalance == null) {
        loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE);
    }
    // 负载均衡选择invoker
    Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);

    //If the `invoker` is in the  `selected` or invoker is unavailable && availablecheck is true, reselect.
    if ((selected != null && selected.contains(invoker))
            || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
        try {
            Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
            if (rinvoker != null) {
                invoker = rinvoker;
            } else {
                //Check the index of current selected invoker, if it's not the last one, choose the one at index+1.
                int index = invokers.indexOf(invoker);
                try {
                    //Avoid collision
                    invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0);
                } catch (Exception e) {
                    logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
                }
            }
        } catch (Throwable t) {
            logger.error("cluster reselect fail reason is :" + t.getMessage() + " if can not solve, you can set cluster.availablecheck=false in url", t);
        }
    }
    return invoker;
}

3. loadBalance源码

AbstractLoadBalance抽象类的核心方法

public abstract class AbstractLoadBalance implements LoadBalance {

    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // invokers列表如果为空,则直接返回空
        if (invokers == null || invokers.isEmpty())
            return null;
        // 如果invokers列表只有1个,则返回第1个
        if (invokers.size() == 1)
            return invokers.get(0);
        return doSelect(invokers, url, invocation);
    }
    // 抽象方法,由子类实现;
    protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);
}

protected int getWeight(Invoker<?> invoker, Invocation invocation) {
    // 获取权重  weight 默认是:100
    int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT);
    if (weight > 0) {
        // 获取remote.timestamp
        // 它表示:服务启动时的时间戳
        long timestamp = invoker.getUrl().getParameter(Constants.REMOTE_TIMESTAMP_KEY, 0L);
        if (timestamp > 0L) {
            // 当前时间-服务启动时的时间戳 
            int uptime = (int) (System.currentTimeMillis() - timestamp);
            // 获取服务预热时间,默认为10分钟
            // warmup,10 * 60 * 1000
            int warmup = invoker.getUrl().getParameter(Constants.WARMUP_KEY, Constants.DEFAULT_WARMUP);
            // 如果服务启动时间还不足10分钟,则计算权重
            if (uptime > 0 && uptime < warmup) {
                weight = calculateWarmupWeight(uptime, warmup, weight);
            }
        }
    }
    return weight;
}
static int calculateWarmupWeight(int uptime, int warmup, int weight) {
    // 当uptime越大的情况下,得到的ww值越大;
    int ww = (int) ((float) uptime / ((float) warmup / (float) weight));
    // ww如果大于weight,则返回weight,否则返回ww
    return ww < 1 ? 1 : (ww > weight ? weight : ww);
}

3.1 RandomLoadBalance 加权随机算法

public class RandomLoadBalance extends AbstractLoadBalance {

    public static final String NAME = "random";

    private final Random random = new Random();

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        int length = invokers.size(); // Number of invokers
        int totalWeight = 0; // The sum of weights
        boolean sameWeight = true; // Every invoker has the same weight?
        for (int i = 0; i < length; i++) {
            // 挨个获取每个invoker的权重;
            int weight = getWeight(invokers.get(i), invocation);
            // 累加到totalWeight里面;
            totalWeight += weight; // Sum
            // 权重发生改变的时候sameWeight=false
            if (sameWeight && i > 0
                    && weight != getWeight(invokers.get(i - 1), invocation)) {
                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.
            // 生成一个小于totalWeight的随机值;
            int offset = random.nextInt(totalWeight);
            // Return a invoker based on the random value.
            for (int i = 0; i < length; i++) {
                // 随机值减去每个invoker的权重
                offset -= getWeight(invokers.get(i), invocation);
                // 当offset小于0时,返回当前invoker;
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        return invokers.get(random.nextInt(length));
    }
}

3.2 LeastActiveLoadBalance 最小活跃数负载均衡

每个服务提供者对应一个活跃数 active。初始情况下,所有服务提供者活跃数均为0。每收到一个请求,活跃数加1,完成请求后则将活跃数减1。在服务运行一段时间后,性能好的服务提供者处理请求的速度更快,因此活跃数下降的也越快,此时这样的服务提供者能够优先获取到新的服务请求、这就是最小活跃数负载均衡算法的基本思想。除了最小活跃数,LeastActiveLoadBalance 在实现上还引入了权重值。

public class LeastActiveLoadBalance extends AbstractLoadBalance {

    public static final String NAME = "leastactive";

    private final Random random = new Random();

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // invokers 个数;
        int length = invokers.size(); // Number of invokers
        // 记录最小的活跃 invoker的活跃值;
        int leastActive = -1; // The least active value of all invokers
        // 具有相同"最小活跃值"的invoker个数;
        int leastCount = 0; // The number of invokers having the same least active value (leastActive)
        // 相同最小活跃值在invokers中的下标
        int[] leastIndexs = new int[length]; // The index of invokers having the same least active value (leastActive)
        // 相同最小活跃值的invoker权重累加;
        int totalWeight = 0; // The sum of with warmup weights
        // 当invoker最小活跃值一样时,判断权重是否一样,则需要记录最小活跃值的权重;
        int firstWeight = 0; // Initial value, used for comparision
        // false表示多个最小活值
        boolean sameWeight = true; // Every invoker has the same weight value?
        for (int i = 0; i < length; i++) {
            Invoker<T> invoker = invokers.get(i);
            // 获取invoker对应的活跃数值
            int active = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()).getActive(); // Active number
            // 获取权重值
            int afterWarmup = getWeight(invoker, invocation); // Weight
            // 如果是第一个invoker或者当前invoker的活跃值小于了最小的invoker
            if (leastActive == -1 || active < leastActive) { // Restart, when find a invoker having smaller least active value.
                // 最小活跃值设置为当前invoker的活跃值
                leastActive = active; // Record the current least active value
                // 最小活跃数量设置;
                leastCount = 1; // Reset leastCount, count again based on current leastCount
                // 记录下标
                leastIndexs[0] = i; // Reset
                totalWeight = afterWarmup; // Reset
                firstWeight = afterWarmup; // Record the weight the first invoker
                sameWeight = true; // Reset, every invoker has the same weight value?
            
                // 如果最小活跃值相同的话
            } else if (active == leastActive) { // If current invoker's active value equals with leaseActive, then accumulating.
                // 记录下标
                leastIndexs[leastCount++] = i; // Record index number of this invoker
                // 累加权重
                totalWeight += afterWarmup; // Add this invoker's weight to totalWeight.
                // If every invoker has the same weight?
                // 判断权重是否一样,如果不一样的话,下面的选择invoker不一样;
                if (sameWeight && i > 0
                        && afterWarmup != firstWeight) {
                    sameWeight = false;
                }
            }
        }
        // 如果只有一个最小活跃invoker的话,直接返回他;
        if (leastCount == 1) {
            // If we got exactly one invoker having the least active value, return this invoker directly.
            return invokers.get(leastIndexs[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 = random.nextInt(totalWeight) + 1;
            // Return a invoker based on the random value.
            // 累减offsetWeight,当offsetWeight<0时,选择该invoker
            for (int i = 0; i < leastCount; i++) {
                int leastIndex = leastIndexs[i];
                offsetWeight -= getWeight(invokers.get(leastIndex), invocation);
                if (offsetWeight <= 0)
                    return invokers.get(leastIndex);
            }
        }
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        // 如果有多个最小活跃值相同的invoker,并且他们的权重都相同,则随机取一个;
        return invokers.get(leastIndexs[random.nextInt(leastCount)]);
    }
}

3.3 ConsistentHashLoadBalance 一致性 hash 算法

一致性hash负载是参考着一致性hash算法来的,大概意思就是在一个环,节点散落在这个环上,每次获取时,生成一个hash值,在这个节点上获取对应的节点;
为了让invoker更随机,一个invoker会生成很多虚拟的invoker散落在hash环上;

public class ConsistentHashLoadBalance extends AbstractLoadBalance {
    public static final String NAME = "consistenthash";

    private final ConcurrentMap<String, ConsistentHashSelector<?>> selectors = new ConcurrentHashMap<String, ConsistentHashSelector<?>>();

    @SuppressWarnings("unchecked")
    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // 调用的方法名
        String methodName = RpcUtils.getMethodName(invocation);
        // 接口+方法名
        String key = invokers.get(0).getUrl().getServiceKey() + "." + methodName;
        // 如果invokers列表没有变化,那么可以将其缓存起来
        int identityHashCode = System.identityHashCode(invokers);
        // 从缓存中获取,如果没有,则创建;
        ConsistentHashSelector<T> selector = (ConsistentHashSelector<T>) selectors.get(key);
        if (selector == null || selector.identityHashCode != identityHashCode) {
            selectors.put(key, new ConsistentHashSelector<T>(invokers, methodName, identityHashCode));
            selector = (ConsistentHashSelector<T>) selectors.get(key);
        }
        return selector.select(invocation);
    }

    private static final class ConsistentHashSelector<T> {

        private final TreeMap<Long, Invoker<T>> virtualInvokers;

        private final int replicaNumber;

        private final int identityHashCode;

        private final int[] argumentIndex;

        ConsistentHashSelector(List<Invoker<T>> invokers, String methodName, int identityHashCode) {
            // 创建一个TreeMap
            this.virtualInvokers = new TreeMap<Long, Invoker<T>>();
            // 记录最新的invokers列表的hashCode值;
            this.identityHashCode = identityHashCode;
            // 获取invokers列表中的第一个invoker 的URL
            URL url = invokers.get(0).getUrl();
            // 获取 hash.nodes 属性,如果没有配置,则默认为160,表示每个invoker均分160个节点到hash环中;
            this.replicaNumber = url.getMethodParameter(methodName, "hash.nodes", 160);
            // 在select时,调用目标接口,目标接口时的,参数;没有配置默认为0;
            // index=["0"]
            String[] index = Constants.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]);
            }
            // argumentIndex[0]=0

            for (Invoker<T> invoker : invokers) {
                // 获取ip+port的值
                String address = invoker.getUrl().getAddress();
                // 这里除4是因为 内部还有个循环,会循环4次;
                for (int i = 0; i < replicaNumber / 4; i++) {
                    // 生成16位的md5值
                    byte[] digest = md5(address + i);
                    // 
                    for (int h = 0; h < 4; h++) {
                        // 生成hash码
                        long m = hash(digest, h);
                        // 放入treeMap中;
                        virtualInvokers.put(m, invoker);
                    }
                }
            }
        }

        // 调用时
        public Invoker<T> select(Invocation invocation) {
            // 生成key
            // invocation.getArguments()调用目标接口时,传入的参数;
            // invocation.getArguments()只有参数值;不是参数名;
            String key = toKey(invocation.getArguments());
            // 生成md5
            byte[] digest = md5(key);
            // 生成hash,这里有个疑问,生成的hash是非常随机的,那怎么获取到对应的invoker呢?
            // 也就是存入TreeMap的invoker规则和获取的规则其实是不一样的;
            return selectForKey(hash(digest, 0));
        }

        private String toKey(Object[] args) {
            // 调用目标接口时,传入的参数;
            StringBuilder buf = new StringBuilder();
            for (int i : argumentIndex) {
                if (i >= 0 && i < args.length) {
                    buf.append(args[i]);
                }
            }
            return buf.toString();
        }

        private Invoker<T> selectForKey(long hash) {
            // 返回在treeMap当前hash值之后map
            // 如果返回为空,则表示生成的hash值过大,则选择treeMap中的第一个返回;
            Map.Entry<Long, Invoker<T>> entry = virtualInvokers.ceilingEntry(hash);
            if (entry == null) {
                entry = virtualInvokers.firstEntry();
            }
            return entry.getValue();
        }

        private long hash(byte[] digest, int number) {
            return (((long) (digest[3 + number * 4] & 0xFF) << 24)
                    | ((long) (digest[2 + number * 4] & 0xFF) << 16)
                    | ((long) (digest[1 + number * 4] & 0xFF) << 8)
                    | (digest[number * 4] & 0xFF))
                    & 0xFFFFFFFFL;
        }

        private byte[] md5(String value) {
            MessageDigest md5;
            try {
                md5 = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            md5.reset();
            byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
            md5.update(bytes);
            return md5.digest();
        }

    }

}

3.4 RoundRobinLoadBalance 加权轮询负载均衡

dubbo的加权轮询算法有点复杂,普通的加权轮训都是搞个[A1,A2,A3,B1,C1 ]都是区间,然后循环这个区间,但这有个问题,就是连续的3个请求都请求到A节点,如果能再分散点是不是更好呢;

参考:http://www.manongjc.com/detail/50-ahzahzevmwfmvey.html

4. loadBalance使用

@Service(loadbalance = "roundrobin",weight = 100,
methods = {@Method(name ="save",loadbalance = "roundrobin",timeout = 4000,retries = 5)})

@Reference(check = false,loadbalance = "roundrobin",cluster = "forking",
methods = {@Method(name="save",loadbalance = "roundrobin",timeout = 1000,retries = 5,async = true)})

Service和Reference都可以指定loadbalance属性,methods也可以指定,因为它都是在消费者调用时,对invokers列表做的一种选择目标invoker,但是weight只能在Service中指定,无法在methods中指定;

  • random 加权随机,默认算法
  • roundrobin 加权轮询
  • leastactive 最少活跃调用数,慢的提供者会接收到更少的请求
  • consistenthash 一致性哈希,相同参数请求同一提供者
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值