欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

dubbo集群容错之loadbalance负载均衡

程序员文章站 2024-03-16 21:08:22
...

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小)

  • dubbo集群容错之loadbalance负载均衡

  • 算法分析:

  • 假设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" />
  • 在业务开发中,我们常把数据持久化到数据库中。如果需要读取这些数据,除了直接从数据库中读取外,为了减轻数据库的访问压力以及提高访问速度,我们更多地引入缓存来对数据进行存取。读取数据的过程一般为:

    dubbo集群容错之loadbalance负载均衡

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

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

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

dubbo集群容错之loadbalance负载均衡

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

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

   一致性哈希算法:

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

dubbo集群容错之loadbalance负载均衡  dubbo集群容错之loadbalance负载均衡

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

dubbo集群容错之loadbalance负载均衡dubbo集群容错之loadbalance负载均衡

一致性哈希算法:

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

dubbo集群容错之loadbalance负载均衡

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

dubbo集群容错之loadbalance负载均衡

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

 

      dubbo集群容错之loadbalance负载均衡dubbo集群容错之loadbalance负载均衡

 

虚拟节点:

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

dubbo集群容错之loadbalance负载均衡

 “虚拟节点”是“实际节点”(物理服务器)在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>