「架构师训练营」第 5 周作业 - 一致性哈希算法
发布于: 2020 年 07 月 06 日
1. 用你熟悉的编程语言实现一致性hash算法
public class ConsistentHash {    /**     * 哈希环最大位置     */    private static final Integer MAX_HASH_POSTION = Integer.MAX_VALUE;    /**     * 节点的虚拟节点数量     */    private final int replicasCount;    /**     * Hash环     */    private final SortedMap<Integer, String> hashCircle = new TreeMap<>();    /**     * 构造方法     *     * @param replicasCount 虚拟节点数量     * @param nodes         节点     */    public ConsistentHash(int replicasCount, Collection<String> nodes) {        this.replicasCount = replicasCount;        nodes.stream().forEach(node -> {            add(node);        });    }      /**     * 添加节点     *     * @param node 节点名称     */    public void add(String node) {        for (int i = 0; i < replicasCount; i++) {            int nodeHash = hash(node + "#" + i);            hashCircle.put(nodeHash, node);        }    }      /**     * 定位节点     *     * @param key 键值     * @return     */    public String get(String key) {        if (hashCircle.isEmpty()) {            return null;        }        int hash = hash(key);        if (hashCircle.containsKey(hash)) {            return hashCircle.get(hash);        }        SortedMap<Integer, String> tailMap = hashCircle.tailMap(hash);        return hashCircle.get(tailMap.isEmpty() ? hashCircle.firstKey() : tailMap.firstKey());    }    /**     * 使用 CRC32 算法     *     * @param key     * @return     */    public int hash(String key) {        CRC32 crc32 = new CRC32();        crc32.update(key.getBytes());        return (int) (crc32.getValue() % MAX_HASH_POSTION);    }}
2. 编写测试用例测试这个算法,测试100万KV数据,10个服务器节点的情况下,计算这些KV数据在服务器上分布数据的标准差,以评估算法的存储负载不均衡性。
标准差按照如下公式计算:

public class ConsistentHashTest {    /**     * 服务器节点数     */    private static final Integer SERVER_COUNT = 10;    /**     * 统计server命中情况     */    Map<String, Integer> serverAllocateCount = new HashMap<>();    /**     * 虚拟节点个数     */    private static final Integer VIRTURAL_NODES_COUNT = 150;    /**     * 一致性哈希算法     */    private ConsistentHash consistentHash;    private static final String source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";    /**     * 初始化一致性哈希算法的hash环     */    @Before    public void init() {        List<String> nodes = new ArrayList<>();        for (int i = 0; i < SERVER_COUNT; i++) {            String server = "testServer-" + i;            serverAllocateCount.put(server, 0);            nodes.add(server);        }        consistentHash = new ConsistentHash(VIRTURAL_NODES_COUNT, nodes);    }    @Test    public void testBalance() {        // 模拟1000000次KV的请求,请求的key随机        for (int i = 0; i < 1000000; i++) {            String server = consistentHash.get(randomString(8));            Integer count = serverAllocateCount.get(server);            serverAllocateCount.put(server, ++count);        }        // 打印落到各个服务器的数量        printServerAllocate(serverAllocateCount);        // 标准差        System.out.println("标准差: " + standardDeviation(serverAllocateCount.entrySet().stream().map(entry -> entry.getValue()).collect(Collectors.toList())));    }    /**     * 生成随机字符串     *     * @param length 字符串长度     * @return     */    public String randomString(int length) {        StringBuffer sb = new StringBuffer();        Random random = new Random();        for (int i = 0; i < length; i++) {            int number = random.nextInt(62);            sb.append(source.charAt(number));        }        return sb.toString();    }      /**     * 打印分布情况     *     * @param serverAllocateCount     * @return     */    private void printServerAllocate(Map<String, Integer> serverAllocateCount) {        System.out.println("服务器分配情况:");        for (Map.Entry<String, Integer> entry : serverAllocateCount.entrySet()) {            System.out.println(String.format("服务器 [%s] 分配到的次数:%d", entry.getKey(), entry.getValue()));        }    }    /**     * 标准差计算     * σ=sqrt(s^2/n)     *     * @param input     * @return     */    private double standardDeviation(List<Integer> input) {        int length = input.size();        if (length == 0) {            return 0;        }        // 和        double sum = 0;        for (Integer num : input) {            sum += num;        }        // 平均值        double average = sum / length;        // 方差        double s = 0;        for (Integer num : input) {            s += ((num - average) * (num - average));        }        return Math.sqrt(s / length);    }}
第一次输出结果:
服务器分配情况:服务器[testServer-9]分配到的次数:77199服务器[testServer-8]分配到的次数:65765服务器[testServer-7]分配到的次数:86737服务器[testServer-6]分配到的次数:97315服务器[testServer-5]分配到的次数:127551服务器[testServer-4]分配到的次数:116312服务器[testServer-3]分配到的次数:97729服务器[testServer-2]分配到的次数:104425服务器[testServer-1]分配到的次数:113640服务器[testServer-0]分配到的次数:113327标准差: 18134.422406021098
第二次输出结果:
服务器分配情况:服务器 [testServer-9] 分配到的次数:77205服务器 [testServer-8] 分配到的次数:65832服务器 [testServer-7] 分配到的次数:86361服务器 [testServer-6] 分配到的次数:97514服务器 [testServer-5] 分配到的次数:127939服务器 [testServer-4] 分配到的次数:116683服务器 [testServer-3] 分配到的次数:97518服务器 [testServer-2] 分配到的次数:103946服务器 [testServer-1] 分配到的次数:113379服务器 [testServer-0] 分配到的次数:113623标准差: 18233.18947962753
第三次输出结果:
服务器分配情况:服务器 [testServer-9] 分配到的次数:77443服务器 [testServer-8] 分配到的次数:65788服务器 [testServer-7] 分配到的次数:87089服务器 [testServer-6] 分配到的次数:97279服务器 [testServer-5] 分配到的次数:127356服务器 [testServer-4] 分配到的次数:115884服务器 [testServer-3] 分配到的次数:97449服务器 [testServer-2] 分配到的次数:103711服务器 [testServer-1] 分配到的次数:113940服务器 [testServer-0] 分配到的次数:114061标准差: 18073.028495523376
标准差
标准差(Standard Deviation) ,是离均差平方的算术平均数的平方根,用σ表示。在概率统计中最常使用作为统计分布程度上的测量。标准差是方差的算术平方根。标准差能反映一个数据集的离散程度。平均数相同的两组数据,标准差未必相同。
 划线
   评论
  复制
发布于: 2020 年 07 月 06 日 阅读数: 57
 
 guoguo 👻
  关注 
还未添加个人签名 2017.11.30 加入
还未添加个人简介
 
 
  
  
 
 
 
  
  
  
  
  
  
  
  
    
评论