• 模仿dubbo的ConsistentHashLoadBalance实现一个自定义负载均衡案例


    通过了解一致性hash原理,这篇文章就够了,可以了解一致性哈希算法的原理,下面就通过一个小案例来事件一下。

    背景

    假设有个系统,用户量非常大,需要针对不同用户访问不同的ip地址进行分流,且要求同一个用户多次访问均为同一个ip。

    这种场景就是典型的哈希一致性问题,下面就通过模仿dubbo的ConsistentHashLoadBalance,来尝试来实现一下。

    原理回顾

    要用一致性哈希算法实现上述需求,需要经过如下几个步骤:

    • 构建一个哈希环
    • 将ip地址均匀的映射到哈希环上
    • 将用户请求映射到哈希环上,从而定位相应的访问IP

    难点分析

    主要的难点在于找到合适的hash算法,hash算法不好,会影响元素的散列程度,这里就直接使用dubbo中的hash算法。

    1. private long hash(byte[] digest, int number) {
    2. return (((long) (digest[3 + number * 4] & 0xFF) << 24)
    3. | ((long) (digest[2 + number * 4] & 0xFF) << 16)
    4. | ((long) (digest[1 + number * 4] & 0xFF) << 8)
    5. | (digest[number * 4] & 0xFF))
    6. & 0xFFFFFFFFL;
    7. }

    构建hash环

    java中可以使用TreeMap来构建哈希环

    private static TreeMap virtualInvokers = new TreeMap();

    将 ip地址映射到哈希环上

    由于是本地实验,就直接在静态代码块中进行,参考ConsistentHashLoadBalance,为了避免ip分布不均,引入虚拟节点。

    1. static {
    2. for (String ip : IP_LIST) {
    3. for (int i = 0; i < VIRTUAL_NODES; i++) {// 虚拟节点,避免分布不均的情况
    4. byte[] digest = Bytes.getMD5(ip + i);
    5. for (int h = 0; h < 4; h++) {
    6. long m = hash(digest, h);
    7. virtualInvokers.put(m, ip);// 映射ip地址
    8. }
    9. }
    10. }
    11. }

    用户请求定位IP

    主要是利用 TreeMap的ceilingEntry 方法,返回与大于或等于给定键元素(ele)的最小键元素链接的键值对,即哈希环顺时针方向第一个ip元素。

    1. public static String getServer(String invocation) {
    2. byte[] digest = Bytes.getMD5(invocation);
    3. long hash = hash(digest, 0);
    4. /**
    5. * 利用 TreeMap的ceilingEntry 方法
    6. * 返回与大于或等于给定键元素(ele)的最小键元素链接的键值对
    7. * 即哈希环顺时针方向第一个ip元素。
    8. */
    9. Map.Entry entry = virtualInvokers.ceilingEntry(hash);
    10. if (entry == null) {// 如果变量整个环都没有,则返回第一个
    11. entry = virtualInvokers.firstEntry();
    12. }
    13. return entry.getValue();
    14. }

    完整测试代码

    1. package com.fast.alibaba.service;
    2. import java.security.MessageDigest;
    3. import java.security.NoSuchAlgorithmException;
    4. import java.util.Map;
    5. import java.util.TreeMap;
    6. /**
    7. * 照搬dubbo的Bytes类中的部分方法,为了方便计算hash
    8. */
    9. class Bytes {
    10. private static final String C64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; //default base64.
    11. private static ThreadLocal MD = new ThreadLocal();
    12. private Bytes() {
    13. }
    14. /**
    15. * get md5.
    16. *
    17. * @param str input string.
    18. * @return MD5 byte array.
    19. */
    20. public static byte[] getMD5(String str) {
    21. return getMD5(str.getBytes());
    22. }
    23. /**
    24. * get md5.
    25. *
    26. * @param source byte array source.
    27. * @return MD5 byte array.
    28. */
    29. public static byte[] getMD5(byte[] source) {
    30. MessageDigest md = getMessageDigest();
    31. return md.digest(source);
    32. }
    33. private static MessageDigest getMessageDigest() {
    34. MessageDigest ret = MD.get();
    35. if (ret == null) {
    36. try {
    37. ret = MessageDigest.getInstance("MD5");
    38. MD.set(ret);
    39. } catch (NoSuchAlgorithmException e) {
    40. throw new RuntimeException(e);
    41. }
    42. }
    43. return ret;
    44. }
    45. }
    46. public class MyConsistentHashLoadBalance {
    47. public static String[] IP_LIST = {"36.4.192.0", "39.153.127.255", "42.55.181.0", "59.44.141.43", "59.44.143.38",
    48. "59.44.143.11", "59.44.143.43", "59.44.143.53", "59.44.143.31", "59.44.143.33", "2.16.10.0",
    49. "2.16.154.0", "2.16.159.0", "2.16.168.0", "2.20.254.0", "2.21.1.0", "2.21.90.0", "2.22.237.0",
    50. "2.23.167.0", "2.56.240.0", "2.59.214.0", "2.60.0.0", "2.92.0.0", "5.1.48.0", "5.2.32.0",
    51. "5.3.0.0", "5.8.0.0", "5.8.19.0", "5.8.36.0"};
    52. private static final int VIRTUAL_NODES = 160;// 虚拟节点个数
    53. /**
    54. * 用 TreeMap构造一个哈希环
    55. */
    56. private static TreeMap virtualInvokers = new TreeMap();
    57. /**
    58. * 将 ip地址映射到哈希环上
    59. */
    60. static {
    61. for (String ip : IP_LIST) {
    62. for (int i = 0; i < VIRTUAL_NODES; i++) {// 虚拟节点,避免分布不均的情况
    63. byte[] digest = Bytes.getMD5(ip + i);
    64. for (int h = 0; h < 4; h++) {
    65. long m = hash(digest, h);
    66. virtualInvokers.put(m, ip);// 映射ip地址
    67. }
    68. }
    69. }
    70. }
    71. public static String getServer(String invocation) {
    72. byte[] digest = Bytes.getMD5(invocation);
    73. long hash = hash(digest, 0);
    74. /**
    75. * 利用 TreeMap的ceilingEntry 方法
    76. * 返回与大于或等于给定键元素(ele)的最小键元素链接的键值对
    77. * 即哈希环顺时针方向第一个ip元素。
    78. */
    79. Map.Entry entry = virtualInvokers.ceilingEntry(hash);
    80. if (entry == null) {// 如果变量整个环都没有,则返回第一个
    81. entry = virtualInvokers.firstEntry();
    82. }
    83. return entry.getValue();
    84. }
    85. public static void main(String[] args) {
    86. for (int i = 0; i < 200; i++) {
    87. System.out.println(getServer("userId" + i) + "---" + i);
    88. }
    89. }
    90. /**
    91. * hash 算法,照抄dubbo中ConsistentHashLoadBalance的
    92. * @param digest
    93. * @param number
    94. * @return
    95. */
    96. private static long hash(byte[] digest, int number) {
    97. return (((long) (digest[3 + number * 4] & 0xFF) << 24)
    98. | ((long) (digest[2 + number * 4] & 0xFF) << 16)
    99. | ((long) (digest[1 + number * 4] & 0xFF) << 8)
    100. | (digest[number * 4] & 0xFF))
    101. & 0xFFFFFFFFL;
    102. }
    103. }

  • 相关阅读:
    HWUI渲染中RenderProxy视角看一种很有用的编程模式
    CocosCreator 面试题(十三)说说Cocos Creator常驻节点
    智能合约漏洞,BEVO 代币损失 4.5 万美元攻击事件分析
    递归算法学习——有效的数独,解数独
    邮件系统利器:MailBee.NET Objects v12.3.1
    Java手写最长递增子序列算法和最长递增子序列算法应用拓展案例
    Vue3中rem适配方案 淘宝flexible在PC端适配
    前端如何将自定义组件注册到全局
    分布式存储--类Redis存储
    京东详情api
  • 原文地址:https://blog.csdn.net/leisure_life/article/details/126787201