负载均衡算法WeightedRoundRobin(加权轮询)简介及实现

文章转载自https://www.cnblogs.com/markcd/p/8456870.html,只有代码是自己实现的,其他全是转载。

Nginx的负载均衡默认算法是加权轮询算法,本文简单介绍算法的逻辑,并给出算法的Java实现版本。

        本文参考了Nginx的负载均衡 - 加权轮询 (Weighted Round Robin) 

        算法简介

        有三个节点{a, b, c},他们的权重分别是{a=5, b=1, c=1}。发送7次请求,a会被分配5次,b会被分配1次,c会被分配1次。

        一般的算法可能是:

        1、轮训所有节点,找到一个最大权重节点;

        2、选中的节点权重-1;

        3、直到减到0,恢复该节点原始权重,继续轮询;

        这样的算法看起来简单,最终效果是:{a, a, a, a, a, b, c},即前5次可能选中的都是a,这可能造成权重大的服务器造成过大压力的同时,小权重服务器还很闲。

        Nginx的加权轮询算法将保持选择的平滑性,希望达到的效果可能是{a, b, a, a, c, a, a},即尽可能均匀的分摊节点,节点分配不再是连续的。

 

        Nginx加权轮询算法

        1、概念解释,每个节点有三个权重变量,分别是:

        (1) weight: 约定权重,即在配置文件或初始化时约定好的每个节点的权重。

        (2) effectiveWeight: 有效权重,初始化为weight。

         在通讯过程中发现节点异常,则-1;

         之后再次选取本节点,调用成功一次则+1,直达恢复到weight;

         此变量的作用主要是节点异常,降低其权重。

        (3) currentWeight: 节点当前权重,初始化为0。

 

        2、算法逻辑

        (1) 轮询所有节点,计算当前状态下所有节点的effectiveWeight之和totalWeight;

        (2) currentWeight = currentWeight + effectiveWeight;  选出所有节点中currentWeight中最大的一个节点作为选中节点;

        (3) 选中节点的currentWeight = currentWeight - totalWeight;

 

        基于以上算法,我们看一个例子:

        这时有三个节点{a, b, c},权重分别是{a=4, b=2, c=1},共7次请求,初始currentWeight值为{0, 0, 0},每次分配后的结果如下: 

请求序号请求前currentWeight值选中节点请求后currentWeight值
1{c=1,b=2,a=4}a{c=1,b=2,a=-3}
2{c=2,b=4,a=1}b{c=2,b=-3,a=1}
3{c=3,b=-1,a=5}a{c=3,b=-1,a=-2}
4{c=4,b=1,a=2}c{c=-3,b=1,a=2}
5{c=-2,b=3,a=6}a{c=-2,b=3,a=-1}
6{c=-1,b=5,a=3}b{c=-1,b=-2,a=3}
7{c=0,b=0,a=7}a{c=0,b=0,a=0}

        观察到七次调用选中的节点顺序为{a, b, a, c, a, b, a},a节点选中4次,b节点选中2次,c节点选中1次,算法保持了currentWeight值从初始值{c=0,b=0,a=0}到7次调用后又回到{c=0,b=0,a=0}。

以下是加权轮询代码实现,还增加了监控器:

package com.aliware.tianchi;

import org.apache.dubbo.rpc.Invocation;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static com.aliware.tianchi.WeightLunxun.Servers.servers;

/**
 * @ClassName WeightLunxun 加权轮询
 * @Date
 * @Author
 * @Description TODO
 **/
public class WeightLunxun {

    static int totalWeight = 0;
    static Server maxWeightServer = null;
    static List currentList = new ArrayList();
    static boolean WARM_UP = false;

    public static void main(String args[]) {

        WeightLunxun.Servers.servers.add(new WeightLunxun.Server(20870));
        WeightLunxun.Servers.servers.add(new WeightLunxun.Server(20880));
        WeightLunxun.Servers.servers.add(new WeightLunxun.Server(20890));
        Monitor.wrappers.add(new Monitor.serverWrapper(WeightLunxun.Servers.servers.get(1)));
        Monitor.wrappers.add(new Monitor.serverWrapper(WeightLunxun.Servers.servers.get(2)));
        Monitor.wrappers.add(new Monitor.serverWrapper(WeightLunxun.Servers.servers.get(0)));

        for (int i = 0; i < 7; i++) {
            System.out.println("第" + (i + 1) + "次轮询开始");
            select();
            System.out.println("第" + (i + 1) + "次轮询结束");
            System.out.println("===============================================");
        }
        Monitor.printMonitors();

    }

    public static Integer select() {

        /*        算法逻辑:
         * 1. 对于每个请求,遍历集群中的所有可用后端,对于每个后端peer执行:
         *     peer->current_weight += peer->effecitve_weight。
         *     同时累加所有peer的effective_weight,保存为total。
         * 2. 从集群中选出current_weight最大的peer,作为本次选定的后端。
         * 3. 对于本次选定的后端,执行:peer->current_weight -= total。
         * */
        Integer result = null;

        currentList.clear();
        servers.forEach(server -> currentList.add(server.currentWeight));
        System.out.println("轮询前currentWeight:" + currentList);
        currentList.clear();
        for (int i = 0; i < servers.size(); i++) {
            Server server = servers.get(i);
            server.currentWeight += server.effectiveWeight;
            totalWeight += server.effectiveWeight;
            if (maxWeightServer != null) {
                if (server.currentWeight > maxWeightServer.currentWeight) {
                    maxWeightServer = server;
                }
            } else {
                maxWeightServer = server;
            }
            currentList.add(server.currentWeight);
        }
        System.out.println("轮询后currentWeight:" + currentList);
        System.out.println("选择节点:" + maxWeightServer.port);
        maxWeightServer.currentWeight -= totalWeight;
        totalWeight = 0;
        for (int i = 0; i < servers.size(); i++) {
            if (maxWeightServer.port == servers.get(i).port) {
                result = i;
            }
        }
        Monitor.addMonitor(result);
        return result;
    }

    public static class Servers {
        
        //服务列表
        static List<Server> servers = new ArrayList<>();

    }

    public static class Server {

        public int weight;
        public int port;
        //有效权重(如果该次请求失败,有效权重理应-1,成功+1,直到等于weight。这里并没有实现)
        public int effectiveWeight;
        //当前权重
        public int currentWeight = 0;

        Server(int port) {
            super();
            this.port = port;
            switch (port) {
                case 20870:
                    this.weight = 2;
                    break;
                case 20880:
                    this.weight = 1;
                    break;
                case 20890:
                    this.weight = 4;
                    break;
                default:
                    ;
            }
            this.effectiveWeight = this.weight;
        }

        Server(int port, String name) {
            super();
            this.weight = weight;
            this.effectiveWeight = this.weight;
        }

        public Server() {

        }
    }

}
package com.aliware.tianchi;

import jdk.nashorn.internal.ir.IfNode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @ClassName Monitor 监控器
 * @Date
 * @Author
 * @Description TODO
 **/
public class Monitor {


    static List<serverWrapper> wrappers = new ArrayList<>();

    public static void addMonitor(int index) {
        WeightLunxun.Server server = WeightLunxun.Servers.servers.get(index);
        wrappers.forEach(wrapper -> {
            if (server.port == wrapper.port)
                wrapper.index++;
        });
    }

    public static void printMonitors() {
        wrappers.forEach(wrapper -> System.out.print(wrapper.port + ":" + wrapper.index + ","));
        System.out.println();
    }

    //包装服务端
    public static class serverWrapper extends WeightLunxun.Server {

        private int index;

        serverWrapper(int port) {
            super(port);
        }

        serverWrapper(int port, String name) {
            super(port, name);
        }

        serverWrapper(WeightLunxun.Server server) {
            this.port = server.port;
            this.currentWeight = server.currentWeight;
            this.effectiveWeight = server.effectiveWeight;
            this.weight = server.weight;
        }

    }

}

以下是输出结果:

第1次轮询开始
轮询前currentWeight:[0, 0, 0]
轮询后currentWeight:[2, 1, 4]
选择节点:20890
第1次轮询结束
===============================================
第2次轮询开始
轮询前currentWeight:[2, 1, -3]
轮询后currentWeight:[4, 2, 1]
选择节点:20870
第2次轮询结束
===============================================
第3次轮询开始
轮询前currentWeight:[-3, 2, 1]
轮询后currentWeight:[-1, 3, 5]
选择节点:20890
第3次轮询结束
===============================================
第4次轮询开始
轮询前currentWeight:[-1, 3, -2]
轮询后currentWeight:[1, 4, 2]
选择节点:20880
第4次轮询结束
===============================================
第5次轮询开始
轮询前currentWeight:[1, -3, 2]
轮询后currentWeight:[3, -2, 6]
选择节点:20890
第5次轮询结束
===============================================
第6次轮询开始
轮询前currentWeight:[3, -2, -1]
轮询后currentWeight:[5, -1, 3]
选择节点:20870
第6次轮询结束
===============================================
第7次轮询开始
轮询前currentWeight:[-2, -1, 3]
轮询后currentWeight:[0, 0, 7]
选择节点:20890
第7次轮询结束
===============================================
20880:1,20890:4,20870:2,

本文结束。如果不足或疑问,请回复交流。

再次声明:文章转载自https://www.cnblogs.com/markcd/p/8456870.html,只有代码是自己实现的,其他全是转载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值