dubbo集群容错之loadbalance负载均衡

LoadBalance

首先查看 LoadBalance 接口

Invoker select(List> invokers, URL url, Invocation invocation) throws RpcException;

LoadBalance 定义了一个方法就是从 invokers 列表中选取一个

AbstractLoadBalance

AbstractLoadBalance 抽象类是所有负载均衡策略实现类的父类,实现了LoadBalance接口 的方法,同时提供抽象方法交由子类实现,

 public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        if (invokers == null || invokers.size() == 0)
            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);

Random LoadBalance

  • 随机,按权重设置随机概率。
  • 在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
  • 算法分析:通过下面这段代码,获取提供者服务的权重,然后计算出总权重,通过总权重计算出offset(根据0~累加的权重值 选出的一个随机数),然后再减去每个服务的的权重,如果发现小于0,则调用当前服务。
  •     protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
            int length = invokers.size();//总个数
            int totalWeight = 0;         //最大权重   
            boolean sameWeight = true;   //权重是否一样
            for (int i = 0; i < length; i++) {
                int weight = getWeight(invokers.get(i), invocation);
                totalWeight += weight;    //累计总权重
                if (sameWeight && i > 0
                        && weight != getWeight(invokers.get(i - 1), invocation)) {
                    sameWeight = false;   //计算所有权重是否一样
                }
            }
                //循环检测,如果offset小于0,则返回该服务                        
            if (totalWeight > 0 && ! sameWeight) {
                       //当权重不相同且权重大于0则按总权重数随机。
                int offset = random.nextInt(totalWeight);
                    //确定随机值落在那个片段上
                for (int i = 0; i < length; i++) {
                    offset -= getWeight(invokers.get(i), invocation);
                    if (offset < 0) {
                        return invokers.get(i);
                    }
                }
            }
            //如果权重相同或者权重为0则均等随机
            return invokers.get(random.nextInt(length));
        }
    

    栗子:结合代码

      3个服务 A,B,C  其中A的权重为1,B的权重为2,C的权重为3,那么invokers中的服务的顺序有6种情况,分别为ABC,BAC,ACB,CAB,BCA,CBA  

  • 此时的 totalWeight=1+2+3=6,此时offset=random.nextInt(totalWeight)=random.nextInt(6)=【0,1,2,3,4,5】中某个随机数

    • 随机碰撞到ABC的情况表
        offset offset offset offset offset offset
      invokers 0 1 2 3 4 5
      ABC A B B C C C
      BAC B B A C C C
      ACB A C C C B B
      CAB C C C A B B
      BCA B B C C C A
      CBA C C C B B  

      文字描述:

      当offset=0的时候,如果invokers=ABC 则调用的是A,因为0-1<0 ; 当offset=4的时候,会经过几个循环

                      ①offset=4-1=3>0,此时offset=3,继续下一步

                      ②offset=3-2=1>0,此时offset=1,继续下一步

                       ③offset=1-3=-2<0,调用   此时调用C

RoundRobin LoadBalance

  • 轮循,按公约后的权重设置轮循比率。
  • 存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
  • 详细步骤:
  • 1)获取轮询key  服务名+方法名

    获取可供调用的invokers个数length

    设置最大权重的默认值maxWeight=0

    设置最小权重的默认值minWeight=Integer.MAX_VALUE

    2)遍历所有Inokers,比较出得出maxWeight和minWeight

    3)如果权重是不一样的

    根据key获取自增序列

    自增序列加一与最大权重取模默认得到currentWeigth

    遍历所有invokers筛选出大于currentWeight的invokers

    设置可供调用的invokers的个数length

    4)自增序列加一并与length取模,从invokers获取invoker

  • public class RoundRobinLoadBalance extends AbstractLoadBalance {
     
        public static final String NAME = "roundrobin";
     
        private final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<String, AtomicPositiveInteger>();
     
        protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
            String key = invokers.get(0).getUrl().getServiceKey() + "." + invocation.getMethodName(); //①获取轮询key(服务名+方法名)
            int length = invokers.size(); // Number of invokers  ②获取可供调用的invokers个数
            int maxWeight = 0; // The maximum weight 最大权重值  ③设置最大权重的默认值为0
            int minWeight = Integer.MAX_VALUE; // The minimum weight 最小权重支持,默认最大
            final LinkedHashMap<Invoker<T>, IntegerWrapper> invokerToWeightMap = new LinkedHashMap<Invoker<T>, IntegerWrapper>();
    		//权重值总数
            int weightSum = 0;
            for (int i = 0; i < length; i++) {//④遍历invokers,比较找出maxWeight和minWeight
    			//获取权重值
                int weight = getWeight(invokers.get(i), invocation);
    			//获取最大和最小权重值
                maxWeight = Math.max(maxWeight, weight); // Choose the maximum weight
                minWeight = Math.min(minWeight, weight); // Choose the minimum weight
                if (weight > 0) {
                    invokerToWeightMap.put(invokers.get(i), new IntegerWrapper(weight));
                    weightSum += weight;
                }
            }
                //⑤如果权重不一样
            AtomicPositiveInteger sequence = sequences.get(key);
            if (sequence == null) {
                sequences.putIfAbsent(key, new AtomicPositiveInteger());
                //⑥根据key获取自增序列
                sequence = sequences.get(key);
            }
                
            int currentSequence = sequence.getAndIncrement();
            if (maxWeight > 0 && minWeight < maxWeight) {
    			//轮询当前值
                //⑦自增序列加一与最大权重取模默认得到currentWeigth
                int mod = currentSequence % weightSum;
                for (int i = 0; i < maxWeight; i++) {
    				//轮询当前值的余数轮询从服务中获取
                    for (Map.Entry<Invoker<T>, IntegerWrapper> each : invokerToWeightMap.entrySet()) {
                        final Invoker<T> k = each.getKey();
                        final IntegerWrapper v = each.getValue();
                        if (mod == 0 && v.getValue() > 0) {
                            return k;
                        }
                        if (v.getValue() > 0) {
                            v.decrement();
                            mod--;
                        }
                    }
                }
            }
            // Round robin
    		//直接轮询找服务
            return invokers.get(currentSequence % length);//自增序列加一并与length取模,从invokers获取invoker
        }
     
        private static final class IntegerWrapper {
            private int value;
     
            public IntegerWrapper(int value) {
                this.value = value;
            }
     
            public int getValue() {
                return value;
            }
     
            public void setValue(int value) {
                this.value = value;
            }
     
            public void decrement() {
                this.value--;
            }
        }
     
    }
    

LeastActive LoadBalance

  • 最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
  • 使慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差会越大。
  • public class LeastActiveLoadBalance extends AbstractLoadBalance {
     
        public static final String NAME = "leastactive";
     
        private final Random random = new Random();
     
        protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
            int length = invokers.size(); // 服务提供者总个数
            int leastActive = -1; // 最小的活跃数
            int leastCount = 0; // 相同最小活跃数的个数
            int[] leastIndexs = new int[length]; // 相同最小活跃数的下标
            int totalWeight = 0; // 总权重
            int firstWeight = 0; // 第一个权重,用户计算是否相同
            boolean sameWeight = true; //是否所有权重相同
            for (int i = 0; i < length; i++) {
                Invoker<T> invoker = invokers.get(i);
                int active = RpcStatus.getStatus(invoker.getUrl(),
    invocation.getMethodName()).getActive(); // 每个服务的活跃数
                int weight = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.WEIGHT_KEY, Constants.DEFAULT_WEIGHT); // 权重
                if (leastActive == -1 || active < leastActive) { //发现最小的活跃数,重新开始
                    leastActive = active; // 记录当前活跃数(最小活跃数)
                    leastCount = 1; // 重新统计相同最小活跃数的个数
                    leastIndexs[0] = i; // 最小活跃数调的下标 坐标id
                    totalWeight = weight; // 权重
                    firstWeight = weight; // 第一个权重
                    sameWeight = true; // 重新设置为权重相同
                } else if (active == leastActive) { // 如果活跃数相同,则记录权重
                    leastIndexs[leastCount++] = i; // 累计相同最小活跃数下标
                    totalWeight += weight; //累计总权重
                    // If every invoker has the same weight?
                    if (sameWeight && i > 0
                            && weight != firstWeight) {
                        sameWeight = false;
                    }
                }
            }
            // assert(leastCount > 0)
            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.
            //如果权重不相同且权重大于0则按总权重数随机
                int offsetWeight = random.nextInt(totalWeight);
                // 确定随机值落在哪个片断上
                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.
    		//如果权重相同或权重为0则均等随机
            return invokers.get(leastIndexs[random.nextInt(leastCount)]);
        }
    }
    

    例子.每个服务有一个活跃计数器,那么我们假如有A,B两个提供者.计数均为0.当A提供者开始处理请求,该计数+1,此时A还没处理完,当处理完后则计数-1.而B请求接收到请求处理得很快.B处理完后A还没处理完,所以此时A,B的计数为1,0.那么当有新的请求来的时候,就会选择B提供者(B的活跃计数比A小)

  • 算法分析:

  • 假设A,B,C,节点的最小活跃数分别是1,1,2权重为1,2,3,则leastIndexs(该数组是最小活跃数组)数组内容为[A,B].A,B的权重是1和2,所以调用A的概率为 1/(1+2) = 1/3,B的概率为 2/(1+2) = 2/3。

  • 其中活跃数的变化是在com.alibaba.dubbo.rpc.filter.ActiveLimitFilter中,如果没有配置dubbo:referenceactives属性,默认是调用前活跃数+1,调用结束-1

     

ConsistentHash LoadBalance

  • 一致性 Hash,相同参数的请求总是发到同一提供者。
  • 当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
  • 算法参见:http://en.wikipedia.org/wiki/Consistent_hashing
  • 缺省只对第一个参数 Hash,如果要修改,请配置 <dubbo:parameter key="hash.arguments" value="0,1" />
  • 缺省用 160 份虚拟节点,如果要修改,请配置 <dubbo:parameter key="hash.nodes" value="320" />
  • 在业务开发中,我们常把数据持久化到数据库中。如果需要读取这些数据,除了直接从数据库中读取外,为了减轻数据库的访问压力以及提高访问速度,我们更多地引入缓存来对数据进行存取。读取数据的过程一般为:

    这里写图片描述

    1.这里通过描述图片加入缓存的数据读取过程

    对于分布式缓存,不同机器上存储不同对象的数据。为了实现这些缓存机器的负载均衡,可以使用式子1来定位对象缓存的存储机器:

    m = hash(图片名称) mod n ——式子1 

三台服务器使用哈希对3 求余,那么一定是0,1,2.这样真好跟服务器编号相同,根据对应的图片名称就可找到对应图片缓存的服务器。

存在问题:当服务器数量发生变化时,会引起缓存雪崩,可能引起整体系统压力过大而崩溃;即便不发生上述情况,缓存的位置几乎都会发生改变,因此需要考虑怎么才能尽量减少受影响的缓存呢?

   一致性哈希算法:

它也是通过取模的方法,但是不是对服务器的数量进行,而是对2^32取模。还是以咱们得到三台服务器为例,这次hash(服务器的IP地址) % 2^32,计算得到的结果一定是在0~2^32-1之间的某个整数,此时就用这个结果数代表服务器,那么三台服务器可以入如下图表示;

  

当我们需要缓存照片时,仍然使用图片名称作为key,计算hash(图片名称)%2^32,那么可以用橘色的点表示,但是到底缓存到哪台服务器上呢?A   为什么?因为从图片位置顺时针最先遇到的服务器就是A,因此将被缓存到服务器A上。

一致性哈希算法:

优点:当某台服务器出现down机,那么原来缓存在服务器B上的图片将被缓存至服务器C中,其中只有原来服务器B缓存的照片位置发生改变,其他照片未改动。

缺陷:hash环的偏斜---->我们理想化的吧三台服务器均匀的映射到hash环上,无奈理想很丰满,显示太骨感,想象的和实际情况往往反差很大。

实际上很有可能出现下面右图的情况:1,2,3,4,6图片均被混村到服务器A上了,服务器B上只有5图片,C服务器上压根没有缓存上图片,这就不开心了。如何解决这样的问题呢?一致性哈希算法提出了“虚拟节点”来解决

      

虚拟节点:

          怎么能均匀分布到真hash环上呢,主要咱们只有三个服务器,没有那么多节点可以分布,想到了分身,记得大话西游牛魔王对打孙悟空,都是牛虱和猴子猴孙的PK,既然没有真正多余的物理服务器节点,那么只能讲现有的物理节点通过虚拟的方法复制出来,成为虚拟节点,加入到hash环中。

 “虚拟节点”是“实际节点”(物理服务器)在hash环上的复制品,一个实际节点能对应过个虚拟节点。引入的虚拟节点越多,混村的分布月均衡。

配置

服务端服务级别

<dubbo:service interface="..." loadbalance="roundrobin" />

客户端服务级别

<dubbo:reference interface="..." loadbalance="roundrobin" />

服务端方法级别

<dubbo:service interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:service>

客户端方法级别

<dubbo:reference interface="...">
    <dubbo:method name="..." loadbalance="roundrobin"/>
</dubbo:reference>

猜你喜欢

转载自blog.csdn.net/z15732621582/article/details/81072242