服务提供者对象构建类

public class Refer {
    private String serverName;
    /**
     * 当前使用该referer的调用数
     *
     * @return
     */
    private int activeRefererCount;

    /**
     * 链接是否可用
     */
    private boolean isAvailable;

    /**
     * 类路径
     */
    private String serviceKey;

    /**
     * 方法名
     */
    private String method;

    /**
     * 提供权重占比
     */
    private int weight;

    public int getActiveRefererCount() {
        return activeRefererCount;
    }

    public void setActiveRefererCount(int activeRefererCount) {
        this.activeRefererCount = activeRefererCount;
    }

    public boolean isAvailable() {
        return isAvailable;
    }

    public void setAvailable(boolean available) {
        isAvailable = available;
    }

    public String getServerName() {
        return serverName;
    }

    public void setServerName(String serverName) {
        this.serverName = serverName;
    }

    public String getServiceKey() {
        return serviceKey;
    }

    public void setServiceKey(String serviceKey) {
        this.serviceKey = serviceKey;
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Refer{" +
                "serverName=" + serverName +
                ", activeRefererCount=" + activeRefererCount +
                ", isAvailable=" + isAvailable +
                ", serviceKey='" + serviceKey + '\'' +
                ", method='" + method + '\'' +
                ", weight=" + weight +
                '}';
    }
}

方案一:轮询权重算法实现类

public class RoundRobinWeightLoadBalance {

    public static List<Refer> refers = new ArrayList<Refer>();
    private static final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<>();
    private static int[] weights = new int[] {5, 1, 1};
    private static String[] names = new String[] {"A", "B", "C"};

    static {
        for (int i = 1; i < 4; i++) {
            Refer refer = new Refer();
            refer.setServerName(names[i-1]);
            refer.setActiveRefererCount(ThreadLocalRandom.current().nextInt(11));
            refer.setAvailable(ThreadLocalRandom.current().nextInt(2) == 1 ? true : false);
            refer.setMethod("sayHello");
            refer.setServiceKey("com.zzx.DemoService");
            refer.setWeight(weights[i-1]);
            refers.add(refer);
        }
    }

    private static Refer roundRobinWeight() {
        String key = refers.get(0).getServiceKey() + "." + refers.get(0).getMethod();
        int length = refers.size();
        //最大权重
        int maxWeight = 0;
        //最小权重
        int minWeight = Integer.MAX_VALUE;
        final LinkedHashMap<Refer, IntegerWrapper> referToWeightMap = new LinkedHashMap<>();
        //权重总和
        int weightSum = 0;

        //下面这个循环主要用于查找最大和最小权重,计算权重总和
        for (int i = 0; i < length; i++) {
            int weight = refers.get(i).getWeight();
            //获取权重最大和最小值
            maxWeight = Math.max(maxWeight, weight);
            minWeight = Math.min(minWeight, weight);
            if(weight > 0) {
                referToWeightMap.put(refers.get(i), new IntegerWrapper(weight));
                //累加权重
                weightSum += weight;
            }
        }

        //获取当前服务对应的调用序列对象 AtomicPositiveInteger,默认为0
        AtomicPositiveInteger sequence = sequences.get(key);
        if(sequence == null) {
            sequences.putIfAbsent(key, new AtomicPositiveInteger());
            sequence = sequences.get(key);
        }

        //获取当前的调用编号
        int currentSequence = sequence.getAndIncrement();
        //如果最小权重小于最大权重,表明服务提供者之间的权重是不相等的
        if(maxWeight > 0 && minWeight < maxWeight) {
            //使用调用编号对权重总和进行取余操作
            int mod = currentSequence % weightSum;
            //进行maxWeight次遍历
            for(int i = 0; i < maxWeight; i++) {
                //遍历 invokerToWeightMap
                for (Map.Entry<Refer, IntegerWrapper> each :  referToWeightMap.entrySet()) {
                    final Refer k = each.getKey();
                    //获取权重包装类数据
                    final IntegerWrapper v = each.getValue();

                    //如果 mod = 0, 且权重大于0, 此时返回相应的Invoker
                    if(mod == 0 && v.getValue() > 0) {
                        return k;
                    }

                    //mod != 0,且权重大于0,此时权重和mod分别进行自减操作
                    if(v.getValue() > 0) {
                        v.decrement();
                        mod--;
                    }
                }
            }
        }

        return refers.get(currentSequence%length);
    }

    private static final class IntegerWrapper {
        private int value;

        public IntegerWrapper(int value) {
            this.value = value;
        }

        public void decrement(){
            this.value--;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }
    }

    public static void main(String[] args) {
        for (Refer refer : refers) {
            System.out.println(refer);
        }

        System.out.println("---------------------------------------------");
        for (int i = 0; i < 8; i++) {
            System.out.println("获取按权重轮询Refer:" + roundRobinWeight());
        }
    }
}

测试结果:

负载均衡算法-轮询权重算法-LMLPHP

总结:该算法权重设置比较大,mod比较大,会导致循环次数比较多,严重影响性能,获得Refer对象的性能。

方案二轮询权重算法如下:

public class RoundRobinWeightLoadBalance2 {

    public static List<Refer> refers = new ArrayList<Refer>();

    private static final ConcurrentMap<String, AtomicPositiveInteger> sequences = new ConcurrentHashMap<>();
    private static final ConcurrentMap<String, AtomicPositiveInteger> indexSeqs = new ConcurrentHashMap<String, AtomicPositiveInteger>();

    private static int[] weights = new int[] {5, 1, 1};
    private static String[] names = new String[] {"A", "B", "C"};

    static {
        for (int i = 1; i < 4; i++) {
            Refer refer = new Refer();
            refer.setServerName(names[i-1]);
            refer.setActiveRefererCount(ThreadLocalRandom.current().nextInt(11));
            refer.setAvailable(ThreadLocalRandom.current().nextInt(2) == 1 ? true : false);
            refer.setMethod("sayHello");
            refer.setServiceKey("com.zzx.DemoService");
            refer.setWeight(weights[i-1]);
            refers.add(refer);
        }
    }

    private static Refer roundRobinWeight() {
        String key = refers.get(0).getServiceKey() + "." + refers.get(0).getMethod();
        int length = refers.size();
        //最大权重
        int maxWeight = 0;
        //最小权重
        int minWeight = Integer.MAX_VALUE;
        final List<Refer> invokerToWeightList = new ArrayList<>();


        //下面这个循环主要用于查找最大和最小权重,计算权重总和
        for (int i = 0; i < length; i++) {
            int weight = refers.get(i).getWeight();
            //获取权重最大和最小值
            maxWeight = Math.max(maxWeight, weight);
            minWeight = Math.min(minWeight, weight);
            if(weight > 0) {
                invokerToWeightList.add(refers.get(i));
            }
        }

        //获取当前服务对应的调用序列对象 AtomicPositiveInteger,默认为0
        AtomicPositiveInteger sequence = sequences.get(key);
        if(sequence == null) {
            sequences.putIfAbsent(key, new AtomicPositiveInteger());
            sequence = sequences.get(key);
        }

        // 获取下标序列对象 AtomicPositiveInteger
        AtomicPositiveInteger indexSeq = indexSeqs.get(key);
        if (indexSeq == null) {
            // 创建 AtomicPositiveInteger,默认值为 -1
            indexSeqs.putIfAbsent(key, new AtomicPositiveInteger(-1));
            indexSeq = indexSeqs.get(key);
        }

        if (maxWeight > 0 && minWeight < maxWeight) {
            length = invokerToWeightList.size();
            while (true) {
                //通过循环,依次获取list的下标
                int index = indexSeq.incrementAndGet() % length;
                //获得每一个请求,当前权重值
                int currentWeight = sequence.get() % maxWeight;

                // 每循环一轮(index = 0),重新计算 currentWeight
                if (index == 0) {
                    currentWeight = sequence.incrementAndGet() % maxWeight;
                }

                // 检测 Invoker 的权重是否大于 currentWeight,大于则返回
                if (invokerToWeightList.get(index).getWeight() > currentWeight) {
                    return invokerToWeightList.get(index);
                }
            }
        }


        return refers.get(sequence.incrementAndGet() % length);
    }

    private static final class IntegerWrapper {
        private int value;

        public IntegerWrapper(int value) {
            this.value = value;
        }

        public void decrement(){
            this.value--;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }
    }

    public static void main(String[] args) {
        for (Refer refer : refers) {
            System.out.println(refer);
        }

        System.out.println("---------------------------------------------");
        for (int i = 0; i < 8; i++) {
            System.out.println("获取按权重轮询Refer:" + roundRobinWeight());
        }
    }
}

测试结果

负载均衡算法-轮询权重算法-LMLPHP

总结

虽然该方案解决权重大时,产生性能的问题。但是从测试结果发现,会导致ServiceA在某一个时刻大量请求并发,增大服务器A的压力。

针对方案二的问题可参考nignx,轮询权重算法实现,使用平滑加权实现。

Nginx 的平滑加权轮询负载均衡。每个服务器对应两个权重,分别为 weight 和 currentWeight。其中 weight 是固定的,currentWeight 会动态调整,初始值为0。当有新的请求进来时,遍历服务器列表,让它的 currentWeight 加上自身权重。遍历完成后,找到最大的 currentWeight,并将其减去权重总和,然后返回相应的服务器即可。

上面描述不是很好理解,下面还是举例进行说明。这里仍然使用服务器 [A, B, C] 对应权重 [5, 1, 1] 的例子说明,现在有7个请求依次进入负载均衡逻辑,选择过程如下:

负载均衡算法-轮询权重算法-LMLPHP

参考链接:

http://dubbo.apache.org/zh-cn/docs/source_code_guide/loadbalance.html

12-26 23:26