负载均衡算法: 简单轮询算法, 平滑加权轮询, 一致性hash算法, 随机轮询, 加权随机轮询, 最小活跃数算法(基于dubbo) java代码实现

发布时间 2023-08-01 19:29:47作者: Code2020

直接上干活

    /**
     * @version 1.0.0
     * @@menu <p>
     * @date 2020/11/17 16:28
     */
    public class LoadBlance {
        static Map<String, Integer> serverWeightMap = new HashMap<>();
    
        static {
            serverWeightMap.put("192.168.1.100", 1);
            serverWeightMap.put("192.168.1.101", 8);
            // 权重为4
            serverWeightMap.put("192.168.1.102", 4);
            serverWeightMap.put("192.168.1.103", 9);
            serverWeightMap.put("192.168.1.104", 2);
            // 权重为3
            serverWeightMap.put("192.168.1.105", 3);
            serverWeightMap.put("192.168.1.106", 4);
            // 权重为2
            serverWeightMap.put("192.168.1.107", 2);
            serverWeightMap.put("192.168.1.108", 8);
            serverWeightMap.put("192.168.1.109", 6);
            serverWeightMap.put("192.168.1.110", 1);
        }
    
        public List<String> MapCasttoList() {
            //这里重新一个map,作为缓冲, 目的是避免服务器上下线带来的问题
            Map<String, Integer> serverMap = getServerMap();
            Set<String> strings = serverMap.keySet();
            List<String> list = new ArrayList<>();
            list.addAll(strings);
            return list;
        }
    
        private Map<String, Integer> getServerMap() {
            Map<String, Integer> serverMap = new HashMap<>();
            serverMap.putAll(serverWeightMap);
            return serverMap;
        }
    
        static AtomicInteger index = new AtomicInteger();
    
        //1: 简单的轮询算法:
        public String Round() {
            List<String> ipList = MapCasttoList();
            if (index.get() >= ipList.size()) {
                index.set(0);
            }
            return ipList.get(index.getAndIncrement() % ipList.size());
        }
    
    
        //2: 随机算法
        public String RandomLoadBlance() {
            List<String> ipList = MapCasttoList();
            int index = new Random().nextInt(ipList.size());
            return ipList.get(index);
        }
    
    
        //3: ip 的hash法: 将ip hash,
        public String IpHashLoadBlance() {
            List<String> ipList = MapCasttoList();
            //获取ipList, 然后计算HashCode, 之后和 size计算出对应的index
            List<Long> ipHashList = ipList.stream().map(ip -> myHashCode(ip)).collect(Collectors.toList());
            int i = new Random().nextInt(ipList.size());
            Long index = ipHashList.get(i) % ipList.size();
            return ipList.get(index.intValue());
        }
    
        //因为 hashCode方法会出现负数,所以这里使用自写的hashCode方法
        private static long myHashCode(String str) {
            long h = 0;
            if (h == 0) {
                int off = 0;
                char val[] = str.toCharArray();
                long len = str.length();
                for (long i = 0; i < len; i++) {
                    h = 31 * h + val[off++];
                }
            }
            return h;
        }

    /**
     * 4: 一致性hash 负载轮询算法
     * https://blog.csdn.net/weixin_43925277/article/details/107989585
     * 原理:  http://www.zsythink.net/archives/1182
     */
    static TreeMap<Integer, String> treeMap = new TreeMap<>();

    static {
        //这里使用的hash环,有1000个虚拟节点, 构建hash环
        List<String> serverIpList = MapCasttoList();
        for (String ip : serverIpList) {
            for (int i = 0; i < 1000; i++) {
                //这里的hash算法,是对 2 ^ 32 次方 取模
                int ipHashCode = FNVHash(ip + i);
                treeMap.put(ipHashCode, ip);
            }
        }
    }

    //str 这里的str 是指使用某个请求的标志(请求名,或者别的),来hash,最终命中hash环对应的ip
    public String consistencyHashLoadBlance(String str) {
        int hashCode = FNVHash(str);
        //找到比这个 hashCode 值大的所有子树
        SortedMap<Integer, String> tailSubMap = treeMap.tailMap(hashCode);
        if (tailSubMap == null) {
            //返回hash环的第一个节点 对应的ip
            return treeMap.get(treeMap.firstKey());
        }
        //否则返回 hash环中,这个 子树的第一个节点对应的ip
        return treeMap.get(tailSubMap.firstKey());
    }

    // 32位的 Fowler-Noll-Vo 哈希算法
    // https://en.wikipedia.org/wiki/Fowler–Noll–Vo_hash_function
    public static int FNVHash(String key) {
        final int p = 16777619;
        Long hash = 2166136261L;
        for (int idx = 0, num = key.length(); idx < num; ++idx) {
            hash = (hash ^ key.charAt(idx)) * p;
        }
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        if (hash < 0) {
            hash = Math.abs(hash);
        }
        return hash.intValue();
    }


    
        /**
         * 5: 基于权重的轮询算法: 平滑加权轮询算法, Nginx的默认轮询算法就是 加权轮询算法
         *  https://my.oschina.net/u/1267069/blog/4437331
         * <p>
         * 思路: 比如 A : 5 , B : 3 , C : 2   (服务器 A,B,C 对应权重分别是 5,3,2)
         * ip: A,B,C
         * weight: 5,3,2 (计算得到 totalWeight = 10)
         * currentWeight: 0,0,0 (当前ip的初始权重都为0)
         * <p>
         * 请求次数: |  currentWeight = currentWeight + weight  |  最大权重为  |  返回的ip为 |  最大的权重 - totalWeight,其余不变
         *      1   |           5,3,2    (0,0,0 + 5,3,2)       |     5      |      A     |      -5,3,2
         *      2   |           0,6,4    (-5,3,2 + 5,3,2)      |     6      |      B     |       0,-4,4
         *      3   |           5,-1,6    (0,-4,4 + 5,3,2)     |     6      |     C      |       5,-1,-4
         *      4   |          10,2,-2    (5,-1,-4 + 5,3,2)    |     10     |     A      |       0,2,-2
         *      5   |           5,5,0                          |     5      |     A      |       -5,5,0
         *      6   |           0,8,2                          |     8      |     B      |       0,-2,2
         *      7   |           5,1,4                          |     5      |     A      |       -5,1,4
         *      8   |           0,4,6                          |     6      |     C      |       0,4,-4
         *      9   |           5,7,-2                         |     7      |     B      |       5,-3,-2
         *      10  |           10,0,0                         |     10     |     A      |        0,0,0
         * <p>
         * 至此结束: 可以看到负载轮询的策略是: A,B,C,A,A,B,A,C,B,A,
         *
         *   循环获取到权重最大的节点,和总权重, 之后将这个权重最大节点的权重 - 总权重, 最后返回这个权重最大节点对应的ip
         */
        public String weightRound(List<ServerNode> serverNodeList) {
            ServerNode selectedServerNode = null;
            int maxWeight = 0;
            int totalWeight = 0;
            for (ServerNode serverNode : serverNodeList) {
                serverNode.incrementWeight();//获取节点的当前权重
                if (serverNode.currentWeight > maxWeight) {
                    //节点的当前权重 大于最大权重, 就将该节点当做是选中的节点
                    maxWeight = serverNode.currentWeight;
                    selectedServerNode = serverNode;
                }
                //计算总的权重
                totalWeight += serverNode.weight;
            }
            // 当循环结束的时候, selectedServerNode就是权重最大的节点,对应的权重为maxWeight,  总的权重为totalWeight
            if (selectedServerNode != null) {
                //将权重最大的这个节点,的权重值减去 总权重
                selectedServerNode.decrementTotal(totalWeight);
                //并返回这个权重对应的 ip
                return selectedServerNode.ip;
            }
            return "";
        }
    
        private List<ServerNode> getServerNodeList(Map<String, Integer> serverMap) {
            List<ServerNode> list = new ArrayList<>();
            for (Map.Entry<String, Integer> entry : serverMap.entrySet()) {
                ServerNode serverNode = new ServerNode(entry.getKey(), entry.getValue());
                list.add(serverNode);
            }
            return list;
        }
    
        private class ServerNode {
            String ip;
            int weight;//初始配置好的权重
            int currentWeight;//当前的权重,初始为 0
    
            public ServerNode(String ip, int weight) {
                this.ip = ip;
                this.weight = weight;
            }
    
            public void decrementTotal(int totalWeight) {
                currentWeight = currentWeight - totalWeight;
            }
    
            public void incrementWeight() {
                currentWeight = currentWeight + weight;
            }
    
            public void setCurrentWeight(int currentWeight) {
                this.currentWeight = currentWeight;
            }
        }
    
    
    
        /**
         * 6: 加权随机
         *  思路:  1: 一个ip的权重为5, 就将这个ip存到list中五次, 然后随机,  简单,但是有缺点 list会很大,执行效率低
         *
         *  思路:  2:  转换思路: 将每一个ip的权重转化为 X轴上的数字, 随机的数字,落在X轴的那个位置, 就返回这个位置对应的 ip
         *        比如: 192.168.1.100 , 3
         *             192.168.1.102 , 6
         *             192.168.1.105 , 4
         *        转换为X轴 为: 0--3-----9---13   可以看到这里需要获取所有权重之和
         *        产生的随机数为: 12 ,
         *        12 和 第一个ip的权重3 比较, 12 >= 3
         *        然后 12 - 3 = 9, 9 和第二个ip的权重6比较, 9 >= 6
         *        然后 9 - 6 =3 , 3 和第三个ip的权重4比较, 3 < 4 跳出---->说明随机的ip 是 192.168.1.105
         *
         * 这里实现思路2
         */
        public String weightRandom(){
            //1: 获取所有ip权重之和
            List<Integer> weightList = new ArrayList<>(getServerMap().values());
            int totalWeight = weightList.stream().mapToInt(e->e.intValue()).sum();
            //2: 产生一个随机数
            int index = new Random().nextInt(totalWeight);
            for (String ip : getServerMap().keySet()) {
                //2.1 获取这个ip对应的权重
                Integer weight = getServerMap().get(ip);
    
                if(index < weight){
                    return ip;
                }
                index = index - weight;
            }
            return "";
        }


        /**
         * 6 : 最小活跃数算法 (这里基于 dubbo的最小活跃数算法)
         * 什么是最小活跃数算法:
         * 一个服务器有一个活跃数active, 当处理一个请求时,active+1, 请求处理完active-1, 并发情况下,这个active的值越小,说明这台服务器性能高, 那么这台服务器就应该多处理些请求
         * <p>
         * 有三种情况
         * 1: 只有一个ip 活跃数最低,那么就返回这个ip
         * 2: 有多个ip 活跃数最低,且权重不同,返回权重最大的那个ip(权重最大的ip 有可能是多个, 这里返回第一个)
         * 3: 有多个ip 活跃数最低,且权重相同, 随机返回
         * <p>
         * 也有特殊情况,加入第一台服务器A,2000年买的,处理请求最大数为100, 第二台机器B,2010年买的处理请求最大数为500, 第三台机器C,2020年买的处理最大请求数为2000,对应的活跃数分别为:A-90, B-400,C-800,如果按照最小活跃数应该A机器
         * 处理请求会更多,但实际上C机器还能够处理1200个请求,所以最小活跃数算法,适用于各个机器性能相近, 处理请求的时间长短,取决于某个请求的计算复杂度
         * <p>
         * 实现思路:
         * 1: 循环list, 如果
         */
        public String leastActiveLoadBalance(List<Ip_active_weight> invokers) {
            int length = invokers.size();
            // 所有invoker活跃数中 最小的活跃数, 初始为-1
            int leastActive = -1;

            // 所有invoker活跃数中,活跃数最小,且相同的个数, 默认0个
            int leastCount = 0;

            // leastIndexes 这个数组存的是最小活跃数对应的下标,  leastIndexes的大小不一定为1, 因为有可能有多个ip对应的活跃数最小,且相同
            int[] leastIndexes = new int[length];

            // 用来存 每个ip对应的权重
            int[] weights = new int[length];

            // 所有 ip的 权重之和, 初始为0
            int totalWeight = 0;
            // 这是一个标准
            int firstWeight = 0;

            // 这是一个标志, 默认true 每一个最小活跃数相同的ip 权重都相同
            boolean sameWeight = true;
            for (int i = 0; i < length; i++) {
                Ip_active_weight invoker = invokers.get(i);
                //获取对应的活跃数
                int active = invoker.getActive();
                //获取对应的权重
                int afterWarmup = invoker.getWeight();
                // 先将权重保存起来
                weights[i] = afterWarmup;
                if (leastActive == -1 || active < leastActive) {
                    //更新 最小活跃数
                    leastActive = active;
                    // 最小的活跃数的个数 加1
                    leastCount = 1;
                    //将 当前invoker就是最小活跃数,将他对应的 下标存入 leastIndexes数组中
                    leastIndexes[0] = i;
                    //叠加总权重
                    totalWeight = afterWarmup;
                    //设置第一个权重,用来作比较,
                    firstWeight = afterWarmup;
                    sameWeight = true;
                } else if (active == leastActive) {
                    leastIndexes[leastCount++] = i;
                    totalWeight += afterWarmup;
                    if (sameWeight && afterWarmup != firstWeight) {
                        //权重不同,将标志位 设置为false, 根据权重大小返回,说明有多个ip,最小活跃数相同,权重不同,置为false, 按照权重大小返回
                        sameWeight = false;
                    }
                }
            }
            //跳出循环时候, 已经找到了 最小活跃数,的集合
            if (leastCount == 1) {
                //活跃数最小的只有一个,直接返回
                return invokers.get(leastIndexes[0]).getIp();
            }

            // sameWeight为false ,说明有多个ip 最小活跃数相同, 权重不同,
            if (!sameWeight && totalWeight > 0) {
                //这里用到的思想是:  X轴, 随机出来一个权重a,落到哪个区域内(a-权重 < 0, 就返回这个权重对应的 ip),
                int offsetWeight = new Random().nextInt(totalWeight);
                for (int i = 0; i < leastCount; i++) {
                    int leastIndex = leastIndexes[i];
                    offsetWeight -= weights[leastIndex];
                    if (offsetWeight < 0) {
                        return invokers.get(leastIndex).getIp();
                    }
                }
            }
            //所有的最小活跃数相同,且权重相同,随机返回
            return invokers.get(leastIndexes[new Random().nextInt(leastCount)]).getIp();
        }

        public List<Ip_active_weight> buildIpActiveWeightList() {
            Map<String, Integer> serverMap = getServerMap();
            List<Ip_active_weight> list = new ArrayList<>(serverMap.keySet().size());
            getServerMap().forEach((key, value) -> {
                Ip_active_weight ip_active_weight = new Ip_active_weight();
                ip_active_weight.setIp(key);
                //这里随机权重(1-10)
                ip_active_weight.setActive(new Random().nextInt(3) + 1);
                ip_active_weight.setWeight(value);
                list.add(ip_active_weight);
            });
            return list;
        }

        class Ip_active_weight {
            String ip;
            //活跃数
            int active;
            //权重
            int weight;

            public String getIp() {
                return ip;
            }

            public void setIp(String ip) {
                this.ip = ip;
            }

            public int getActive() {
                return active;
            }

            public void setActive(int active) {
                this.active = active;
            }

            public int getWeight() {
                return weight;
            }

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

            @Override
            public String toString() {
                return "Ip_active_weight{" +
                        "ip='" + ip + '\'' +
                        ", active=" + active +
                        ", weight=" + weight +
                        '}';
            }
        }

        @Test
        public static void test01() {
            LoadBlance loadBlance = new LoadBlance();
            List<Ip_active_weight> invokes = loadBlance.buildIpActiveWeightList();
            for (Ip_active_weight invoke : invokes) {
                System.out.println(invoke);
            }
            System.out.println("_----------------------");
            for (int i = 0; i < 50; i++) {
                //模拟请求50次
                System.out.println(loadBlance.leastActiveLoadBalance(invokes));
            }
        }
    

        public static void main(String[] args) {
            LoadBlance loadBlance = new LoadBlance();
            Map<String, Integer> serverMap = new HashMap<>();
            serverMap.put("A",5);
            serverMap.put("B",3);
            serverMap.put("C",2);
            List<ServerNode> serverNodeList = loadBlance.getServerNodeList(serverMap);
            for (int i = 0; i < 50; i++) {//模拟请求50次
                System.out.println(loadBlance.weightRound(serverNodeList));
            }
        }
    
    
    }