week5. 课后作业
发布于: 2020 年 07 月 05 日
用你熟悉的编程语言实现一致性 hash 算法。
package com.demo.hash;/** * 一致性哈希系统配置文件 * @author niki-lauda * @create 2020-07-05 17:04 */public class SystemConstansts { /** * 实际机器简称-IP地址:端口 */ public static String NODE_LIST = "'M0;192.168.10.1:8306','M1;192.168.10.2:8306','M2;192.168.10.3:8306','M3;192.168.10.4:8306','M4;192.168.10.5:8306','M5;192.168.10.6:8306'," + "'M6;192.168.10.7:8306','M7;192.168.10.8:8306','M8;192.168.10.9:8306','M9;192.168.10.10:8306'"; /** * 每台机器对应虚拟节点数量 */ public static final int VIRTUAL_NODE_NUM = 220;}
package com.demo.hash;import java.util.HashMap;import java.util.Map;/** * 机器 * @author niki-lauda * @create 2020-07-05 17:12 */public class MachineNode { private String name; private String ipAddress; private Map<Object, Object> data; MachineNode(String name, String ipAddress) { this.name = name; this.ipAddress = ipAddress; data = new HashMap<>(); } Object getValue(Object key) { Object value = data.get(key); System.out.println("获取数据从[" + name + "],地址=[" + ipAddress + "],key=[" + key + "],value=[" + value + "]"); return value; } Object putValue(Object key, Object value) { data.put(key, value); System.out.println("新加数据从[" + name + "],地址=[" + ipAddress + "],key=[" + key + "],value=[" + value + "]"); return value; } String getName() { return name; } int getDataSize() { return data.size(); }}
package com.demo.hash;/** * 虚拟节点 * @author niki-lauda * @create 2020-07-05 17:18 */public class VirtualNode { private int position; private MachineNode machineNode; VirtualNode(int position, MachineNode machineNode) { this.position = position; this.machineNode = machineNode; } Object getValue(Object key) { return machineNode.getValue(key); } Object putValue(Object key, Object value) { return machineNode.putValue(key,value); } String getName() { return machineNode.getName(); } int getDataSize() { return machineNode.getDataSize(); }}
package com.demo.hash;import java.util.HashMap;import java.util.Map;import java.util.TreeMap;/** * 一致性哈希环 * @author niki-lauda * @create 2020-07-05 17:24 */public class ConsistentHashRing { private TreeMap<Integer, VirtualNode> nodeMap; private static final ConsistentHashRing INSTANCE = new ConsistentHashRing(); private Map<String, Integer> machineDataSizeMap = new HashMap<>(); private ConsistentHashRing() { this.nodeMap = new TreeMap<>(); startUp(); } public static ConsistentHashRing getInstance () { return INSTANCE; } private void startUp() { String nodeList = SystemConstansts.NODE_LIST; String[] nodeArray = nodeList.split(","); for (String nodeStr : nodeArray) { String[] data = nodeStr.split(";"); machineDataSizeMap.put(data[0], 0); MachineNode machineNode = new MachineNode(data[0], data[1]); for (int i = 0; i < SystemConstansts.VIRTUAL_NODE_NUM; i++) { int nextInt = getHashCode(data[0] + i); nodeMap.put(nextInt, new VirtualNode(nextInt, machineNode)); } } } public int getHashCode(String key) { // int ret = key.hashCode(); // 结果太聚集,不使用 final int p = 16777619; int ret = (int) 2166136261L; for (int i = 0; i < key.length(); i++) { ret = (ret ^ key.charAt(i)) * p; } ret += ret << 13; ret ^= ret >> 7; ret += ret << 3; ret ^= ret >> 17; ret += ret << 5; return ret; } public void addMachine(String machineConfig) { String[] configArray = machineConfig.split(";"); machineDataSizeMap.put(configArray[0], 0); MachineNode machineNode = new MachineNode(configArray[0], configArray[1]); for (int i = 0; i < SystemConstansts.VIRTUAL_NODE_NUM; i++) { int nextInt = getHashCode(configArray[0] + i); nodeMap.put(nextInt, new VirtualNode(nextInt, machineNode)); } } private VirtualNode getRightNode(int key) { Map.Entry<Integer, VirtualNode> integerVirtualNodeEntry = nodeMap.ceilingEntry(key); if(integerVirtualNodeEntry == null) { integerVirtualNodeEntry = nodeMap.firstEntry(); } return integerVirtualNodeEntry.getValue(); } public Object get(Object key) { VirtualNode rightMachine = getRightNode(getHashCode(key.toString())); return rightMachine.getValue(key); } public Object put(Object key, Object value) { VirtualNode rightMachine = getRightNode(getHashCode(key.toString())); return rightMachine.putValue(key, value); } private Map<String, Integer> getMachineDataSizeMap() { for (Map.Entry<String, Integer> entry : machineDataSizeMap.entrySet()) { for (Map.Entry<Integer, VirtualNode> nodeEntry : nodeMap.entrySet()) { if(entry.getKey().equals(nodeEntry.getValue().getName())) { entry.setValue(nodeEntry.getValue().getDataSize()); break; } } } return machineDataSizeMap; } public double getStandardDeviation() { Map<String, Integer> machineDataSizeMap = getMachineDataSizeMap(); int machineSize = machineDataSizeMap.size(); long dataSize = 0L; for (Integer value : machineDataSizeMap.values()) { dataSize += value; } double averageSize = dataSize / machineSize; long total = 0; for (Integer value : machineDataSizeMap.values()) { total += (averageSize - value) * (averageSize - value); } return Math.sqrt(total / (machineSize - 1)); }}
package com.demo.hash;import java.util.Random;/** * @author niki-lauda * @create 2020-07-05 18:29 */public class TestHash { private static String getKey() { Random random = new Random(); int nextInt = random.nextInt(15); nextInt = nextInt > 5 ? nextInt : 5; char start = 'A'; String result = ""; for (int i = 0; i < nextInt; i++) { int nextInt1 = random.nextInt(58); result += (char)(start + nextInt1); } return result; } public static void main(String[] args) { ConsistentHashRing instance = ConsistentHashRing.getInstance(); for (int i = 0; i < 1000000; i++) { String key = getKey(); instance.put(key, key); } System.out.println(instance.getStandardDeviation()); }}
编写测试用例测试这个算法,测试 100 万 KV 数据,10 个服务器节点的情况下,计算这些 KV 数据在服务器上分布数量的标准差,以评估算法的存储负载不均衡性。
220时方差为5-10万
200时方差为9-10W
180时方差为13-17万
160时方差为9-15万
150时方差为12-13万
修改hashCode计算方式为
public int getHashCode(String key) { // int ret = key.hashCode(); // 结果太聚集,不使用 final int p = 16777619; int ret = (int) 2166136261L; for (int i = 0; i < key.length(); i++) { ret = (ret ^ key.charAt(i)) * p; } ret += ret << 13; ret ^= ret >> 7; ret += ret << 3; ret ^= ret >> 17; ret += ret << 5; return ret; }
200节点时方差降为5000+,这个计算Hash分布均匀真厉害。
划线
评论
复制
发布于: 2020 年 07 月 05 日 阅读数: 96
版权声明: 本文为 InfoQ 作者【个人练习生niki】的原创文章。
原文链接:【http://xie.infoq.cn/article/18114aaa891a228665c5abdbf】。文章转载请联系作者。
个人练习生niki
关注
做一个真诚、坦诚的行人,追求自由。 2018.07.30 加入
还未添加个人简介
评论