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

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

时间:2023-08-01 19:33:09浏览次数:44  
标签:weight 权重 int ip 轮询 算法 随机 active public

直接上干活

    /**
     * @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));
            }
        }
    
    
    }

标签:weight,权重,int,ip,轮询,算法,随机,active,public
From: https://www.cnblogs.com/cxy2020/p/17598859.html

相关文章

  • 算法题目
    第一章动态规划数字三角形模型[线性DP]摘花生最低通行费数字三角形方格取数最长上升子序列模型[线性DP]最长上升子序列怪盗基德的滑翔翼登山合唱队形友好城市最大上升子序列和导弹拦截[贪心]导弹防御系统[dfs+贪心]背包问题[组合类]......
  • 淘宝H5商品详情数据解析接口sign算法接口代码教程
    淘宝H5商品详情数据解析接口sign算法接口代码教程如下:1.公共参数名称类型必须描述(接口代码教程wx19970108018)keyString是调用key(必须以GET方式拼接在URL中,点击获取请求key和secret)secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_search,item_get,item......
  • 限流算法
    Tokenbucketalgorithm令牌桶算法该算法用具有预定义令牌容量的桶进行类比,这个桶会定期以恒定速率填充令牌。令牌可以被视为某种特定大小的数据包。因此,每次我们收到请求时,算法都会检查存储桶中的令牌,每个请求应该至少有一个令牌才可以被转发以进一步处理。令牌桶的算法流程......
  • 随机高并发查询结果一致性设计实践
    一、前言物流合约中心是京东物流合同管理的唯一入口。为商家提供合同的创建,盖章等能力,为不同业务条线提供合同的定制,归档,查询等功能。由于各个业务条线众多,为各个业务条线提供高可用查询能力是物流合约中心重中之重。同时计费系统在每个物流单结算时,都需要查询合......
  • 排序算法---快速排序
    什么是快速排序?快速排序(QuickSort)是一种高效的排序算法,它使用分治法来将一个数组分成两个子数组,然后对这两个子数组分别进行排序,最后将它们合并成有序的数组。快速排序的基本步骤:1.选择一个基准元素(pivot):从数组中选择一个元素作为基准元素。通常选择数组的第一个元素或者最后......
  • 代码随想录算法训练营第五天|力扣242.有效的字母异位词、力扣242.两个数组的交集、力
    哈希表哈希表理论基础哈希表,又称为散列表(HashTable),是根据关键码的值而直接进行访问的数据结构其中,数组就是一张哈希表;表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素哈希表解决的问题:一般哈希表都是用来快速判断一个元素是否出现在集合中哈希函数:把学生的......
  • 代码随想录算法训练营第三天| LeetCode 242.有效的字母异位词 349. 两个数组的交集
    242.有效的字母异位词    卡哥建议: 这道题目,大家可以感受到数组用来做哈希表给我们带来的遍历之处。    题目链接/文章讲解/视频讲解: https://programmercarl.com/%E5%93%88%E5%B8%8C%E8%A1%A8%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html   做题思路:......
  • 随机不重复数组
    创建一个长度为6的int型数组,要求数组元素的值都在1-30之间,且是随机赋值。同时,要求元素的值各不相同。 /***各个位置数字不同*一直随机即可*思路:*若某个位置数字相同eg:位置1和位置2数字相同*arr[1]=arr[2]需重新随机数字但重新随......
  • 数据结构与算法(三):单向链表
    链表定义链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑是通过链表种的指针链接次序实现的。链表由一系列节点组成,每个节点包括两部分:一个是存储数据元素的数据域,一个是存储下一个节点地址的指针域。单向链表从头节点(也可以没有头节点)开始,指针指向下一个节点的......
  • 排序算法
    时间复杂度:由于计算机的性能不同,无法准确地确定一个算法的执行时间因此使用执行算法的次数来代表算法的时间复杂度一般用O(公式)来表示空间复杂度:执行一个程序(算法)所需要的内存空间的大小,是对一个算法在运行过程中临时占用存储空间大小的衡量通常来说,只要这个算法不涉及动......