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


直接上干活

    /**
     * @version 1.0.0
     * @@menu 

* @date 2020/11/17 16:28 */ public class LoadBlance { static Map 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 MapCasttoList() { //这里重新一个map,作为缓冲, 目的是避免服务器上下线带来的问题 Map serverMap = getServerMap(); Set strings = serverMap.keySet(); List list = new ArrayList<>(); list.addAll(strings); return list; } private Map getServerMap() { Map serverMap = new HashMap<>(); serverMap.putAll(serverWeightMap); return serverMap; } static AtomicInteger index = new AtomicInteger(); //1: 简单的轮询算法: public String Round() { List ipList = MapCasttoList(); if (index.get() >= ipList.size()) { index.set(0); } return ipList.get(index.getAndIncrement() % ipList.size()); } //2: 随机算法 public String RandomLoadBlance() { List ipList = MapCasttoList(); int index = new Random().nextInt(ipList.size()); return ipList.get(index); } //3: ip 的hash法: 将ip hash, public String IpHashLoadBlance() { List ipList = MapCasttoList(); //获取ipList, 然后计算HashCode, 之后和 size计算出对应的index List 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 treeMap = new TreeMap<>(); static { //这里使用的hash环,有1000个虚拟节点, 构建hash环 List 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 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 *

* 思路: 比如 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) *

* 请求次数: | 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 *

* 至此结束: 可以看到负载轮询的策略是: A,B,C,A,A,B,A,C,B,A, * * 循环获取到权重最大的节点,和总权重, 之后将这个权重最大节点的权重 - 总权重, 最后返回这个权重最大节点对应的ip */ public String weightRound(List 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 getServerNodeList(Map serverMap) { List list = new ArrayList<>(); for (Map.Entry 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 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的值越小,说明这台服务器性能高, 那么这台服务器就应该多处理些请求 *

* 有三种情况 * 1: 只有一个ip 活跃数最低,那么就返回这个ip * 2: 有多个ip 活跃数最低,且权重不同,返回权重最大的那个ip(权重最大的ip 有可能是多个, 这里返回第一个) * 3: 有多个ip 活跃数最低,且权重相同, 随机返回 *

* 也有特殊情况,加入第一台服务器A,2000年买的,处理请求最大数为100, 第二台机器B,2010年买的处理请求最大数为500, 第三台机器C,2020年买的处理最大请求数为2000,对应的活跃数分别为:A-90, B-400,C-800,如果按照最小活跃数应该A机器 * 处理请求会更多,但实际上C机器还能够处理1200个请求,所以最小活跃数算法,适用于各个机器性能相近, 处理请求的时间长短,取决于某个请求的计算复杂度 *

* 实现思路: * 1: 循环list, 如果 */ public String leastActiveLoadBalance(List 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 buildIpActiveWeightList() { Map serverMap = getServerMap(); List 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 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 serverMap = new HashMap<>(); serverMap.put("A",5); serverMap.put("B",3); serverMap.put("C",2); List serverNodeList = loadBlance.getServerNodeList(serverMap); for (int i = 0; i < 50; i++) {//模拟请求50次 System.out.println(loadBlance.weightRound(serverNodeList)); } } }