【Dubbo】负载均衡

一. 概述

版本:2.7.8

解决问题

  • 当有多个服务提供者时,避免请求集中到其中一个或多个,导致负载过高,服务不可用,需要做一定的负载均衡策略。
  • Dubbo提供了多种均衡策略,默认为random,也就是每次加权随机调用一台服务提供者的服务。

二. Dubbo负载均衡模式

Random LoadBalance:加权随机

  • 按照概率设置权重,比较均匀,并且可以动态调节提供者的权重。

RoundRobin LoadBalance:轮询

  • 轮询,按公约后的权重设置轮询比率。会存在执行比较慢的服务提供者堆积请求的情况,比如一个机器执行得非常慢,但是机器没有宕机(如果宕机了,那么当前机器会从ZooKeeper 的服务列表中删除)。
  • 当很多新的请求到达该机器后,由于之前的请求还没处理完,会导致新的请求被堆积,久而久之,消费者调用这台机器上的所有请求都会被阻塞。

LeastActive LoadBalance:最少活跃调用数

  • 如果每个提供者的活跃数相同,则随机选择一个。
  • 在每个服务提供者里维护着一个活跃数计数器,用来记录当前同时处理请求的个数,也就是并发处理任务的个数。这个值越小,说明当前服务提供者处理的速度越快或者当前机器的负载比较低,所以路由选择时就选择该活跃度最小的机器。
  • 如果一个服务提供者处理速度很慢,由于堆积,同时处理的请求就比较多,也就是说活跃调用数较大,处理速度慢。这时,处理速度慢的提供者将收到更少的请求。

ConsistentHash LoadBalance一致性Hash策略

  • 一致性Hash,可以保证相同参数的请求总是发到同一提供者,当某一台提供者机器宕机时,原本发往该提供者的请求,将基于虚拟节点平摊给其他提供者,这样就不会引起剧烈变动。

三. LoadBalance接口及实现类结构图

在这里插入图片描述


四. 源码解析

1. 接口LoadBalance

  • 负载均衡模式默认为:random
package org.apache.dubbo.rpc.cluster;

@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {

    /**
     * select one invoker in list.
     */
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

}

2. AbstractLoadBalance抽象类

  • 使用模板模式执行公共业务
  • 各个实现类实现抽象方法(doSelect)
  • getWeight:从URL中获取各个服务提供者的权重
package org.apache.dubbo.rpc.cluster.loadbalance;

public abstract class AbstractLoadBalance implements LoadBalance {

   @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        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);

    /**
     * Get the weight of the invoker's invocation which takes warmup time into account
     * if the uptime is within the warmup time, the weight will be reduce proportionally
     *
     * @param invoker    the invoker
     * @param invocation the invocation of this invoker
     * @return weight
     */
    int getWeight(Invoker<?> invoker, Invocation invocation) {

        int weight;
        URL url = invoker.getUrl();
        // Multiple registry scenario, load balance among multiple registries.
        if (REGISTRY_SERVICE_REFERENCE_PATH.equals(url.getServiceInterface())) {
            weight = url.getParameter(REGISTRY_KEY + "." + WEIGHT_KEY, DEFAULT_WEIGHT);
        } else {
            weight = url.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;
                    }
                    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);
    }
}

3. RandomLoadBalance加权随机算法

RandomLoadBalance实现类


package org.apache.dubbo.rpc.cluster.loadbalance;

public class RandomLoadBalance extends AbstractLoadBalance {

    public static final String NAME = "random";

    /**
     * 加权随机从invokers中选择一个
     * @param invokers invokers集合
     * @param url URL
     * @param invocation Invocation
     * @param <T>
     * @return 选择的invoker
     */
    @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.
            // 使用 ThreadLocalRandom.current().nextInt(length)从所有服务提供者里随机选择一个服务提供者进行调用。需要注意的是,这里没有使用Random而是使用了ThreadLocalRandom,这是出于性能上的考虑,因 为Random在高并发下会导致大量线程竞争同一个原子变量,导致大量线程原地自旋,从而浪费CPU资源
            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. 其它模式(参考源码)


五. 实现自定义LoadBalance

1. 消费端使用

2. 功能:自定义负载均衡实现

3. 定义实现类

MyLoadBalance实现类

package org.apache.dubbo.rpc.cluster.loadbalance;

public class MyLoadBalance extends AbstractLoadBalance {

    @Override
    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {

        // 自定义复杂均衡算法,从invokers中选择一个返回
    }
}

引入SPI

  • resources新增文件夹META-INF.dubbo
  • 新建文件:org.apache.dubbo.rpc.cluster.LoadBalance
  • 文件内容:myLoadBalance=org.apache.dubbo.rpc.cluster.loadbalance.MyLoadBalance

六. 使用自定义LoadBalance

public static void main(String[] args) {

        // 1.创建服务引用对象实例
        ReferenceConfig<GreetingService> referenceConfig = new ReferenceConfig<>();

        // 2.设置应用程序信息
        referenceConfig.setApplication(new ApplicationConfig("dubbo-consumer"));

        // 3.设置服务注册中心
        referenceConfig.setRegistry(new RegistryConfig("ZKAddress"));

        // 4.设置服务接口和超时时间
        referenceConfig.setInterface(GreetingService.class);
        referenceConfig.setTimeout(5000);

        // 5.设置自定义负载均衡
        referenceConfig.setLoadbalance("myLoadBalance");

        // 6.设置服务分组与版本
        referenceConfig.setVersion("1.0.0");
        referenceConfig.setGroup("dubbo");

        // 7.引用服务
        GreetingService greetingService = referenceConfig.get();
        
    }
posted @ 2021-02-02 14:42    阅读(230)  评论(0编辑  收藏  举报