微服务架构专题六:负载均衡算法 (二)一致性哈希和最小活跃数算法

一、一致性哈希算法 —ConsistentHashLoadBalance

服务器集群接收到一次请求调用时,可以根据请求的信息,比如客户端的IP地址,或请求路径与请求参数等信息进行哈希,可以得到一个哈希值,特点是对于相同的IP地址,或请求路径与请求参数哈希出来的值一样的,只要能再添加一个算法,就能够把哈希值映射成一个服务端IP地址,就可以使相同的请求(相同的IP地址,或请求路径与请求参数)落到同一服务器上。
因为客户段发起的请况是无穷无尽的(客户端地址不同,请求参数不同等等),所以对于其哈希值也是无穷大的,所以我们不鞥把所有的哈希值都进行映射到服务端IP上,所以这里需要用户哈希环。如下图:
在这里插入图片描述
在这里插入图片描述

上面这情况是比较均匀情况,如果出现ip4服务器不存在,那就是下面这样了:

会发现,ip3和ip1之间的范围是比较发的,会有更多的请求落在ip1上,这是不“公平的”,解决这个问题需要加入虚拟节点,比如:
在这里插入图片描述
其实ip2-1,ip3-1就是虚拟节点,并不能处理节点,而是等同于对于的ip2和ip3服务器。
实际上,这只是处理这种不均衡性的一种思路,实际上,就算哈希环本身是均衡的,你也可以增加更多的虚拟节点来使这个环更加平滑,比如:
在这里插入图片描述

这个彩环也是“公平的”,并且只有ip1,2,3,4是实际上的服务器ip,其他的都是虚拟ip。

那么我们怎么来实现呢?
对于我们的服务器ip地址,我们肯定知道总共有多少个,需要多少个虚拟节点也由我们自己控制,虚拟节点越多则流量越均衡,另外哈希算法也是很关键的,哈希算法越散列流量也将越均衡。

实现:

public class ServerIps {
    public static final List<String> LIST = Arrays.asList(
            "192.168.0.1",
            "192.168.0.2",
            "192.168.0.3",
            "192.168.0.4",
            "192.168.0.5",
            "192.168.0.6",
            "192.168.0.7",
            "192.168.0.8",
            "192.168.0.9",
            "192.168.0.10"
    );
    
/**
 * 哈希环算法
 */
public class ConsistentHash {
    private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();
    private static final int VIRTUAL_NODES = 160;
    static {
        // 对每个真实节点添加虚拟节点,虚拟节点会根据哈希算法进行散列
        for (String ip : ServerIps.LIST) {
            for (int i = 0; i < VIRTUAL_NODES; i++) {
                int hash = getHash(ip+"VN"+i);
                virtualNodes.put(hash, ip);
            }
        }
    }
    private static String getServer(String client) {
        int hash = getHash(client);
        // 得到大于该Hash值的排好序的Map
        SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
        // 大于该hash值的第一个元素的位置
        Integer nodeIndex = subMap.firstKey();
        // 如果不存在大于该hash值的元素,则返回根节点
        if (nodeIndex == null) {
            nodeIndex = virtualNodes.firstKey();
            return virtualNodes.get(nodeIndex);
        }
        //返回对应的虚拟节点名称
        return subMap.get(nodeIndex);
    }

    //hash算法
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++)
            hash = (hash ^ str.charAt(i)) * 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;
    }

    public static void main(String[] args) {
        // 连续调用10次,随机10个client
        for (int i = 0; i < 10; i++) {
            System.out.println(getServer("client" + i));
        }
    }

}


二、最小活跃数算法 — LeastActiveLoadbalance

在这里插入图片描述

实现:
因为活跃数是需要服务器请求处理相关逻辑配合的,一次调用开始时活跃数+1,结束时活跃数-1,所以这里就不对部分逻辑进行模拟来,只是使用一个map来进行模拟。

public class ServerIps {
    public static final Map<String, Integer> WEIGHT_LIST = new HashMap<String, Integer>();
    static {
        // 权重之和为50
        WEIGHT_LIST.put("192.168.0.1", 1);
        WEIGHT_LIST.put("192.168.0.2", 8);
        WEIGHT_LIST.put("192.168.0.3", 3);
        WEIGHT_LIST.put("192.168.0.4", 6);
        WEIGHT_LIST.put("192.168.0.5", 5);
        WEIGHT_LIST.put("192.168.0.6", 5);
        WEIGHT_LIST.put("192.168.0.7", 4);
        WEIGHT_LIST.put("192.168.0.8", 7);
        WEIGHT_LIST.put("192.168.0.9", 2);
        WEIGHT_LIST.put("192.168.0.10", 9);
    }

    // 服务器当前的活跃数
    public static final Map<String, Integer> ACTIVITY_LIST = new LinkedHashMap<>();
    static {
        ACTIVITY_LIST.put("192.168.0.1", 2);
        ACTIVITY_LIST.put("192.168.0.2", 0);
        ACTIVITY_LIST.put("192.168.0.3", 1);
        ACTIVITY_LIST.put("192.168.0.4", 3);
        ACTIVITY_LIST.put("192.168.0.5", 0);
        ACTIVITY_LIST.put("192.168.0.6", 1);
        ACTIVITY_LIST.put("192.168.0.7", 4);
        ACTIVITY_LIST.put("192.168.0.8", 2);
        ACTIVITY_LIST.put("192.168.0.9", 7);
        ACTIVITY_LIST.put("192.168.0.10", 3);
    }
}

/**
 * 最小活跃数算法
 */
public class LeastActive {
    private static String getServer() {
        // 找出当前活跃数最小的服务器
        Optional<Integer> minValue =
                ServerIps.ACTIVITY_LIST.values().stream().min(Comparator.naturalOrder());
        if (minValue.isPresent()) {
                List<String> minActivityIps = new ArrayList<>();
                ServerIps.ACTIVITY_LIST.forEach((ip, activity) -> {
                    if (activity.equals(minValue.get())) {
                        minActivityIps.add(ip);
                    }
                });
            // 最小活跃数的ip有多个,则根据权重来选,权重大的优先
            if (minActivityIps.size() > 1) {
                // 过滤出对应的ip和权重
                Map<String, Integer> weightList = new LinkedHashMap<>();
                ServerIps.WEIGHT_LIST.forEach((ip, weight) -> {
                    if (minActivityIps.contains(ip)) {
                        weightList.put(ip, ServerIps.WEIGHT_LIST.get(ip));
                    }
                });
                int totalWeight = 0;
                boolean sameWeight = true; // 如果所以权重都相等,那么随机一个ip就好了
                Object[] weights = weightList.values().toArray();
                for (int i = 0; i < weights.length; i++) {
                    Integer weight = (Integer) weights[i];
                    totalWeight += weight;
                    if (sameWeight && i > 0 && !weight.equals(weights[i - 1])) {
                        sameWeight = false;
                    }
                }

                java.util.Random random = new java.util.Random();
                int randomPos = random.nextInt(totalWeight);
                if (!sameWeight) {
                    for (String ip : weightList.keySet()) {
                        Integer value = weightList.get(ip);
                        if (randomPos < value) {
                            return ip;
                        }
                        randomPos = randomPos - value;
                    }
                }
                return (String) weightList.keySet().toArray()[new java.util.Random().nextInt(weightList.size())];

            } else {
                return minActivityIps.get(0);
            }
        } else {
            return (String) ServerIps.WEIGHT_LIST.keySet().toArray()
                    [new java.util.Random().nextInt(ServerIps.WEIGHT_LIST.size())];
        }
    }
    public static void main(String[] args) {
        // 连续调用10次,随机10个client
        for (int i = 0; i < 10; i++) {
        System.out.println(getServer());
        }
    }
}

这里因为不会对活跃数进行操作,所以结果都是固定的(担任在随机权重的时候会随机,具体看源码实现,以及运行结果即可理解。)
每台服务器的活跃数具体实现要由框架去记录 一个请求的生命周期。

三、负载均衡总结

在这里插入图片描述

以上均为鲁班学院学习资料,欢迎大家报班学习,真心推荐!

发布了143 篇原创文章 · 获赞 49 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/weixin_36586564/article/details/103922565