1、准备工作—hash算法

import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * @Describe: 实现一致性哈希算法中使用的哈希函数, 使用MD5算法来保证一致性哈希的平衡性 * @Author: liuyj * @Date: 2022-08-26 16:36 */ public class HashFunction { private MessageDigest md5 = null; public long hash(String key) { if (md5 == null) { try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("no md5 algrithm found"); } } md5.reset(); md5.update(key.getBytes()); byte[] bKey = md5.digest(); //具体的哈希函数实现细节--每个字节 & 0xFF 再移位 long result = ((long) (bKey[3] & 0xFF) << 24) | ((long) (bKey[2] & 0xFF) << 16 | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF)); return result & 0xffffffffL; } }
2、一致性hash算法

1 import java.util.Collection; 2 import java.util.HashSet; 3 import java.util.Iterator; 4 import java.util.Set; 5 import java.util.SortedMap; 6 import java.util.SortedSet; 7 import java.util.TreeMap; 8 import java.util.TreeSet; 9 10 /** 11 * @Describe:一致性hash函数 12 * @Author: liuyj 13 * @Date: 2022-08-26 16:40 14 */ 15 public class ConsistentHash<T> { 16 private final HashFunction hashFunction; 17 private final int numberOfReplicas; 18 19 // 节点的复制因子,实际节点个数 * numberOfReplicas =虚拟节点个数 20 private final SortedMap<Long,Object> circle = new TreeMap<>(); 21 22 // 存储虚拟节点的hash值到真实节点的映射 23 public ConsistentHash(HashFunction hashFunction, int numberOfReplicas, 24 Collection<T> nodes){ 25 this.hashFunction = hashFunction; 26 this.numberOfReplicas = numberOfReplicas; 27 for (T node : nodes) 28 add(node); 29 } 30 31 public void add(T node){ 32 // 对于一个实际机器节点 node, 对应 numberOfReplicas 个虚拟节点 33 for (int i = 0; i < numberOfReplicas; i++){ 34 /* 35 * 不同的虚拟节点(i不同)有不同的hash值,但都对应同一个实际机器node 36 * 虚拟node一般是均衡分布在环上的,数据存储在顺时针方向的虚拟node上 37 */ 38 circle.put(hashFunction.hash(node.toString() + i), node); 39 } 40 } 41 42 public void remove(T node){ 43 for (int i = 0; i < numberOfReplicas; i++) 44 circle.remove(hashFunction.hash(node.toString() + i)); 45 } 46 47 /** 48 * 获得一个最近的顺时针节点,根据给定的key 取Hash 49 * 然后再取得顺时针方向上最近的一个虚拟节点对应的实际节点 50 * 再从实际节点中取得 数据 51 */ 52 public T get(Object key){ 53 if (circle.isEmpty()){ 54 return null; 55 } 56 long hash = hashFunction.hash((String) key); 57 58 // node 用String来表示,获得node在哈希环中的hashCode 59 if (!circle.containsKey(hash)) { 60 //数据映射在两台虚拟机器所在环之间,就需要按顺时针方向寻找机器 61 SortedMap tailMap = circle.tailMap(hash); 62 hash = tailMap.isEmpty() ? circle.firstKey() : (long) tailMap.firstKey(); 63 } 64 return (T) circle.get(hash); 65 } 66 67 public long getSize(){ 68 return circle.size(); 69 } 70 71 // 查看MD5算法生成的hashCode值---表示整个哈希环中各个虚拟节点位置 72 public void testBalance(){ 73 74 Set sets = circle.keySet(); 75 //获得TreeMap中所有的Key 76 SortedSet<Long> sortedSets= new TreeSet(sets); 77 //将获得的Key集合排序 78 for(Long hashCode : sortedSets){ 79 System.out.println(hashCode); 80 } 81 System.out.println("----each location 's distance are follows: ----"); 82 83 // 查看用MD5算法生成的long hashCode 相邻两个hashCode的差值 84 Iterator it = sortedSets.iterator(); 85 Iterator it2 = sortedSets.iterator(); 86 if(it2.hasNext()){ 87 it2.next(); 88 } 89 90 long keyPre, keyAfter; 91 while(it.hasNext() && it2.hasNext()){ 92 keyPre = (long) it.next(); 93 keyAfter = (long) it2.next(); 94 System.out.println(keyAfter - keyPre); 95 } 96 } 97 98 public static void main(String[] args){ 99 Set<String> nodes = new HashSet<>(); 100 nodes.add("A"); 101 nodes.add("B"); 102 nodes.add("C"); 103 ConsistentHash consistentHash = new ConsistentHash(new HashFunction(), 2, nodes); 104 consistentHash.add("D"); 105 System.out.println("hash circle size: " + consistentHash.getSize()); 106 System.out.println("location of each node are follows: "); 107 consistentHash.testBalance(); 108 } 109 }
3、具体应用—解决数据迁移问题讲解
3.1如何解决集群中添加或者删除机器上需要迁移大量数据的问题?
假设采用传统的哈希取模法,设有K台物理机,H(key)=hash(key) mod K 即可实现数据分片。但当K变化时(如新增一台机器),所有已经映射好的数据都需要重新再映射。H(key)=hash(key) mod (K+1)。
一致性哈希采用的做法如下:
- 引入一个环的概念。
- 先将机器映射到这个环上,再将数据也通过相同的哈希函数映射到这个环上,数据存储在它顺时针走向的那台机器上。
- 以环为中介,实现了数据与机器数目之间的解藕。
- 这样,当机器的数目变化时,只会影响到增加或删除的那台机器所在的环的邻接机器的数据存储,而其他机器上的数据不受影响。
在具体JAVA实现代码中,定义了一个TreeMap用来保存虚拟机器节点到实际的物理机器的映射。机器以字符串形式来标识,故hash函数的参数为String
1 2 3 4 5 6 7 8 | // 对于一个实际机器节点 node, 对应 numberOfReplicas 个虚拟节点 for (int i = 0; i < numberOfReplicas; i++){ /* * 不同的虚拟节点(i不同)有不同的 hash 值,但都对应同一个实际机器node * 虚拟node一般是均衡分布在环上的,数据存储在顺时针方向的虚拟node上 */ circle.put(hashFunction. hash (node.toString() + i), node); } |
而对于 数据的存储而言,逻辑上是按顺时针方向存储在虚拟机器节点中,虚拟机器节点通过TreeMap知道它实际需要将数据存储在哪台物理机器上。此外,TreeMap中的Key是有序的,而环也是顺时针有序的,这样才能当数据被映射到两台虚拟机器之间的弧上时,通过TreeMap的 tailMap()来寻找顺时针方向上的下一台虚拟机。
1 2 3 4 5 | if (!circle.containsKey( hash )) { // 数据映射在两台虚拟机器所在环之间,就需要按顺时针方向寻找机器 SortedMap tailMap = circle.tailMap( hash ); hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey(); } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)