架构师训练营第 5 周课后练习
发布于: 2020 年 10 月 24 日
题目
用你熟悉的编程语言实现一致性 hash 算法。
编写测试用例测试这个算法,测试 100 万 KV 数据,10 个服务器节点的情况下,计算这些 KV 数据在服务器上分布数量的标准差,以评估算法的存储负载不均衡性。
解答
一致性hash类
public class ConsistentHash { /** * 虚拟节点到真实节点的映射 */ private Map<Integer, String> virtualNodeToNodeMap = new TreeMap(); /** * 真实节点到虚拟节点的映射 */ private Map<String, Set<Integer>> nodeToVirtualNodeMap = new HashMap<>(); /** * 数据存储,key为nodeId,value为数据,数据类型为Map<String, String> */ private Map<String, Map<String, String>> dataMap = new HashMap<>(); private Random random = new Random(); /** * 查询真实节点nodeId * * @param key 数据的key值 * @return 真实节点nodeId */ private String findNode(String key) { int dataMod = Math.abs(key.hashCode() % Integer.MAX_VALUE); int virtualNode = search(dataMod, virtualNodeToNodeMap.keySet()); return virtualNodeToNodeMap.get(virtualNode); } /** * 保存数据 * * @param key 数据key值 * @param value 数据value值 */ public void put(String key, String value) { String nodeId = findNode(key); Map<String, String> stringStringMap = dataMap.computeIfAbsent(nodeId, k -> new HashMap<>()); stringStringMap.put(key, value); } /** * 查询数据 * * @param key 数据key值 * @return 数据value值 */ public String get(String key) { String nodeId = findNode(key); return dataMap.get(nodeId) == null ? null : dataMap.get(nodeId).get(key); } /** * 二分查找数据匹配的虚拟节点 * * @param dataMod 数据key值hash取余 * @param nodeSet 虚拟节点位置集合 * @return 虚拟节点位置 */ private int search(int dataMod, Set<Integer> nodeSet) { List<Integer> nodeList = new ArrayList<>(nodeSet); int left = 0; int right = nodeList.size() - 1; //数据dataMod比最后一个节点大或者比第一个节点小,匹配到第一个节点 if (dataMod > nodeList.get(right) || dataMod < nodeList.get(left)) { return nodeList.get(0); } while (left < right) { int mid = (left + right) / 2; if (nodeList.get(mid) == dataMod) { return nodeList.get(mid); } else if (nodeList.get(mid) > dataMod) { right = mid; } else { left = mid + 1; } } return nodeList.get(right); } /** * 添加节点 * * @param nodeId 真实节点Id * @param virtualNodeNum 虚拟节点个数 */ public void addNode(String nodeId, int virtualNodeNum) { for (int i = 0; i < virtualNodeNum; i++) { int round = random.nextInt(Integer.MAX_VALUE); if (!virtualNodeToNodeMap.containsKey(round)) { virtualNodeToNodeMap.put(round, nodeId); Set<Integer> integers = nodeToVirtualNodeMap.computeIfAbsent(nodeId, k -> new HashSet<>()); integers.add(round); nodeToVirtualNodeMap.put(nodeId, integers); } } } /** * 删除节点 * * @param nodeId 真实节点Id */ public void removeNode(String nodeId) { Set<Integer> removedVirtualNodeSet = nodeToVirtualNodeMap.remove(nodeId); if (CollectionUtils.isNotEmpty(removedVirtualNodeSet)) { for (Integer virtualNode : removedVirtualNodeSet) { virtualNodeToNodeMap.remove(virtualNode); } } dataMap.remove(nodeId); } /** * 打印标准差 */ public void print() { //计算总数据量 AtomicInteger sum = new AtomicInteger(); nodeToVirtualNodeMap.forEach((k, v) -> { int i = dataMap.get(k) == null ? 0 : dataMap.get(k).size(); sum.addAndGet(i); }); //计算方差 AtomicReference<Double> sumPow = new AtomicReference<>((double) 0); nodeToVirtualNodeMap.forEach((k, v) -> { double pow = Math.pow((dataMap.get(k).size() - (sum.doubleValue() / nodeToVirtualNodeMap.size())), 2); sumPow.updateAndGet(v1 -> v1 + pow); }); //打印标准差 System.out.println("标准差:" + Math.sqrt(sumPow.get() / sum.doubleValue())); }}
测试类
public class ConsistentHashTest { long[] testData = new long[1000000]; @Before public void before() { Random random = new Random(); for (int i = 0; i < 1000000; i++) { long l = random.nextLong(); testData[i] = l; } } @Test public void test() { System.out.print("虚拟节点数:1,"); test0(1); for (int i = 10; i <= 300; i += 10) { System.out.print("虚拟节点数:" + i + ","); test0(i); } } private void test0(int virtualNodeNum) { ConsistentHash consistentHash = new ConsistentHash(); for (int i = 0; i < 10; i++) { consistentHash.addNode("node" + i, virtualNodeNum); } for (int i = 0; i < 1000000; i++) { consistentHash.put("" + testData[i], "" + testData[i]); } consistentHash.print(); }}
测试结果
虚拟节点数:1,标准差:201.50569043081637虚拟节点数:10,标准差:99.73175044087013虚拟节点数:20,标准差:79.58521105079762虚拟节点数:30,标准差:47.06226511760776虚拟节点数:40,标准差:65.39111537510276虚拟节点数:50,标准差:27.08199379661697虚拟节点数:60,标准差:48.66145834230618虚拟节点数:70,标准差:47.294346998346434虚拟节点数:80,标准差:34.054695270990166虚拟节点数:90,标准差:45.663282624007664虚拟节点数:100,标准差:36.52003608979597虚拟节点数:110,标准差:24.84981621662422虚拟节点数:120,标准差:29.891592262708254虚拟节点数:130,标准差:27.797646375187956虚拟节点数:140,标准差:33.411939303189214虚拟节点数:150,标准差:18.821508919318877虚拟节点数:160,标准差:27.064991483464393虚拟节点数:170,标准差:23.53887452704568虚拟节点数:180,标准差:32.69040620732633虚拟节点数:190,标准差:15.280666215842816虚拟节点数:200,标准差:24.98085154673475虚拟节点数:210,标准差:26.587770609812324虚拟节点数:220,标准差:14.074486775722944虚拟节点数:230,标准差:24.10723708764652虚拟节点数:240,标准差:14.091914135418225虚拟节点数:250,标准差:16.858168939715842虚拟节点数:260,标准差:15.62551701544624虚拟节点数:270,标准差:13.93321527860673虚拟节点数:280,标准差:15.521547409971726虚拟节点数:290,标准差:16.375284547146045虚拟节点数:300,标准差:9.322771798129567
划线
评论
复制
发布于: 2020 年 10 月 24 日阅读数: 31
叶纪想
关注
还未添加个人签名 2018.05.23 加入
还未添加个人简介
评论