用你熟悉的编程语言实现一致性 hash 算法。
    编写测试用例测试这个算法,测试 100 万 KV 数据,10 个服务器节点的情况下,计算这些 KV 数据在服务器上分布数量的标准差,以评估算法的存储负载不均衡性。

    1. package com.example.design.hash;
    2. import org.springframework.util.StringUtils;
    3. import java.util.LinkedList;
    4. import java.util.List;
    5. import java.util.SortedMap;
    6. import java.util.TreeMap;
    7. /**
    8. * 带虚拟节点的一致性Hash算法
    9. */
    10. public class ConsistentHashingWithVirtualNode {
    11. /**
    12. * 待添加入Hash环的服务器列表
    13. */
    14. public static String[] servers =
    15. {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111", "192.168.0.3:111", "192.168.0.4:111",
    16. "192.168.0.5:111", "192.168.0.6:111", "192.168.0.7:111", "192.168.0.8:111", "192.168.0.9:111"};
    17. private static List<String> realNodes = new LinkedList<>();
    18. private static SortedMap<Integer, String> virtualNodes = new TreeMap<>();
    19. /**
    20. * /虚拟节点的数目, 一个真实节点对应的虚拟节点数
    21. */
    22. private static final int VIRTUAL_NODES = 100;
    23. static {
    24. //先把原始的服务器添加到真实结点列表中
    25. for (int i = 0; i < servers.length; i++) {
    26. realNodes.add(servers[i]);
    27. }
    28. //再添加虚拟节点,遍历LinkedList使用foreach循环效率会比较高
    29. for (String str : realNodes) {
    30. for (int i = 0; i < VIRTUAL_NODES; i++) {
    31. String virtualNodeName = str + "&&VN" + String.valueOf(i);
    32. int hash = getHash(virtualNodeName);
    33. // System.out.println("虚拟节点[" + virtualNodeName + "]被添加, hash值为" + hash);
    34. virtualNodes.put(hash, virtualNodeName);
    35. }
    36. }
    37. System.out.println();
    38. }
    39. /**
    40. * 使用FNV1_32_HASH算法计算服务器的Hash值
    41. * @param str
    42. * @return
    43. */
    44. public static int getHash(String str) {
    45. final int p = 16777619;
    46. int hash = (int)2166136261L;
    47. for (int i = 0; i < str.length(); i++) {
    48. hash = (hash ^ str.charAt(i)) * p;
    49. }
    50. hash += hash << 13;
    51. hash ^= hash >> 7;
    52. hash += hash << 3;
    53. hash ^= hash >> 17;
    54. hash += hash << 5;
    55. // 如果算出来的值为负数则取其绝对值
    56. if (hash < 0) {
    57. hash = Math.abs(hash);
    58. }
    59. return hash;
    60. }
    61. /**
    62. * 获取应当路由到的结点
    63. * @param key
    64. * @return
    65. */
    66. public static String getServer(String key) {
    67. //得到该key的hash值
    68. int hash = getHash(key);
    69. // 得到大于该Hash值的所有Map
    70. SortedMap<Integer, String> subMap = virtualNodes.tailMap(hash);
    71. String virtualNode;
    72. if (subMap.isEmpty()) {
    73. //如果没有比该key的hash值大的,则从第一个node开始
    74. Integer i = virtualNodes.firstKey();
    75. //返回对应的服务器
    76. virtualNode = virtualNodes.get(i);
    77. } else {
    78. //第一个Key就是顺时针过去离node最近的那个结点
    79. Integer i = subMap.firstKey();
    80. //返回对应的服务器
    81. virtualNode = subMap.get(i);
    82. }
    83. //virtualNode虚拟节点名称要截取一下
    84. if (!StringUtils.isEmpty(virtualNode)) {
    85. return virtualNode.substring(0, virtualNode.indexOf("&&"));
    86. }
    87. return null;
    88. }
    89. }
    1. package com.example.design.hash;
    2. import java.util.Arrays;
    3. import java.util.Map;
    4. import java.util.TreeMap;
    5. /**
    6. * @description: 测试 100 万 KV 数据,10 个服务器节点的情况下,
    7. * 计算这些 KV 数据在服务器上分布数量的标准差,以评估算法的存储负载不均衡性。
    8. * @author: jcwang
    9. * @create: 2020-07-08 10:00
    10. **/
    11. public class Test {
    12. /**
    13. * 计算标准方差
    14. *
    15. * @param array
    16. * @return
    17. */
    18. public static double calcStd(Integer[] array) {
    19. double avg = Arrays.stream(array).mapToInt(Integer::intValue).average().orElse(0d);
    20. double avgStd =
    21. Arrays.stream(array).map(count -> Math.pow(count - avg, 2)).mapToDouble(Double::doubleValue).average()
    22. .orElse(0d);
    23. return Math.sqrt(avgStd);
    24. }
    25. public static void main(String[] args) {
    26. int keyNum = 10000;
    27. Map<String, Integer> serverCount = new TreeMap<>();
    28. for (String server : ConsistentHashingWithVirtualNode.servers) {
    29. serverCount.put(server, 0);
    30. }
    31. String serverName = "";
    32. String key = "";
    33. for (int i = 0; i < keyNum; i++) {
    34. key = "key" + System.currentTimeMillis();
    35. serverName = ConsistentHashingWithVirtualNode.getServer(key);
    36. serverCount.put(serverName, serverCount.get(serverName) + 1);
    37. }
    38. Integer[] array = new Integer[serverCount.size()];
    39. double rs = calcStd(serverCount.values().toArray(array));
    40. System.out.println("rs = " + rs);
    41. }
    42. }

    作业提交地址: https://jinshuju.net/f/0zLWY4