一致性hash算法,参考:
http://www.blogjava.net/hello-yun/archive/2012/10/10/389289.html
针对这篇文章,加入了自己的理解,在原有的代码上进行了修改。
https://github.com/luoqg/my-code/blob/master/j-algorithm/src/main/java/com/luoq/algorithm/consistencyhash/ConsistencyHash.java
1 /** 2 * 一致性hash 的java 实现 3 * @author luoqiang 4 * @data 2016/11/08 5 */ 6 public class ConsistencyHash { 7 8 public ConsistencyHash(List<Node> shards){ 9 shards = shards; 10 init(); 11 } 12 13 private static class Node{ 14 private String name; 15 private String ip; 16 17 public Node(String name, String ip) { 18 this.name = name; 19 this.ip = ip; 20 } 21 22 public String getName() { 23 return name; 24 } 25 26 public void setName(String name) { 27 this.name = name; 28 } 29 30 public String getIp() { 31 return ip; 32 } 33 34 public void setIp(String ip) { 35 this.ip = ip; 36 } 37 38 @Override 39 public String toString() { 40 return "Node{" + 41 "ip='" + ip + '\'' + 42 ", name='" + name + '\'' + 43 '}'; 44 } 45 } 46 47 private static class Client{ 48 public Client(String name, Long hashCode) { 49 this.name = name; 50 this.hashCode = hashCode; 51 } 52 53 public Client(String name) { 54 this.name = name; 55 } 56 57 private String name; 58 private Long hashCode; 59 60 public String getName() { 61 return name; 62 } 63 64 public void setName(String name) { 65 this.name = name; 66 } 67 68 public Long getHashCode() { 69 return hashCode; 70 } 71 72 public void setHashCode(Long hashCode) { 73 this.hashCode = hashCode; 74 } 75 } 76 77 private static TreeMap<Long,Node> nodes;//虚拟节点hash值 到 真实主机 的映射 78 79 private static TreeMap<Long,Node> treeKey;//客户端hash值 到 真实节点 的映射 80 81 private static List<Node> shards = new ArrayList<Node>();//真实主机 82 83 private static List<Client> cliends = new ArrayList<Client>();//客户端 84 85 private static TreeMap<Long,Client> clientTree;//客户端自己hash 和客户端的映射 86 87 private static final int NODE_NUM = 100;//每个机器节点关联的虚拟节点个数 88 89 90 private void init(){ 91 nodes = new TreeMap<Long, Node>(); 92 treeKey = new TreeMap<Long, Node>(); 93 clientTree = new TreeMap<Long, Client>(); 94 for (int i = 0; i < shards.size(); i++) { 95 final Node shardInfo = shards.get(i); 96 for (int n = 0; n < NODE_NUM; n++) { 97 Long key = hash("SHARD-"+shardInfo.name+"-NODE-"+n); 98 nodes.put(key,shardInfo); 99 } 100 } 101 } 102 103 /** 104 * 添加一个真实主机 105 */ 106 private void addNode(Node n){ 107 System.out.println("添加主机"+n+"的变化:"); 108 for (int i = 0; i < NODE_NUM; i++) { 109 Long lg = hash("SHARD-"+n.name+"-NODE-"+i); 110 SortedMap<Long,Node> head = nodes.headMap(lg); 111 SortedMap<Long,Node> between; 112 if(head.size() == 0){ 113 between = treeKey.tailMap(nodes.lastKey());//建立在 最后一个虚拟主机的hash值 不和 客户端的hash值相等。 114 }else{ 115 Long begin = head.lastKey(); 116 between = treeKey.subMap(begin,lg); 117 } 118 nodes.put(lg,n); 119 for(Iterator<Long> it=between.keySet().iterator();it.hasNext();){ 120 Long lo = it.next(); 121 treeKey.put(lo, nodes.get(lg)); 122 } 123 } 124 } 125 126 /** 127 * 删除一个真实主机 128 * @param n 129 */ 130 private void deleteNode(Node n){ 131 System.out.println("删除主机"+n+"的变化:"); 132 for (int i = 0; i < NODE_NUM; i++) { 133 Long virturalHashCode = hash("SHARD-" + n.name + "-NODE-" + i); 134 SortedMap<Long,Node> tail = nodes.tailMap(virturalHashCode);// 等于和大于 此值 == 顺时针 环形此值后面 135 SortedMap<Long,Node> head = nodes.headMap(virturalHashCode);// 严格小于 此值(不等于) == 顺时针 环形此值前面 136 SortedMap<Long, Node> between; 137 if(head.size() == 0){ 138 between = treeKey.tailMap(nodes.lastKey()); 139 }else{ 140 Long begin = head.lastKey(); 141 Long end = tail.firstKey(); 142 /** 143 * 方法用于返回此映射的键值从fromKey(包括)到toKey(不包括)的部分视图。 144 * (如果fromKey和toKey相等,则返回映射为空)返回的映射受此映射支持, 145 * 因此改变返回映射反映在此映射中,反之亦然。 146 */ 147 between = treeKey.subMap(begin,end);//在n节点的第i个虚拟节点的所有key的集合 148 } 149 nodes.remove(virturalHashCode);//从nodes中删除n节点的第i个虚拟节点 150 for(Iterator<Long> it = between.keySet().iterator();it.hasNext();){ 151 Long lo = it.next(); 152 treeKey.put(lo, nodes.get(tail.firstKey())); 153 } 154 } 155 } 156 157 /** 158 * 客户端hash值 映射 到 真实主机 159 */ 160 private void keyToNode(List<Client> clients){ 161 for (Client client : clients) { 162 Long hashCode = hash(client.getName()); 163 SortedMap<Long, Node> tail = nodes.tailMap(hashCode); // 沿环的顺时针找到一个虚拟节点 164 Node node = tail.size() == 0 ? nodes.get(nodes.firstKey()) : nodes.get(tail.firstKey()); 165 treeKey.put(hashCode,node); 166 client.setHashCode(hashCode); 167 clientTree.put(hashCode,client); 168 } 169 } 170 171 /** 172 * 输出客户端 映射到 真实主机 173 */ 174 private void printKeyTree(){ 175 for(Iterator<Long> it = treeKey.keySet().iterator();it.hasNext();){ 176 Long lo = it.next(); 177 System.out.println(clientTree.get(lo).name+"(hash:"+lo+")连接到主机->"+treeKey.get(lo)); 178 } 179 } 180 181 /** 182 * MurMurHash算法,是非加密HASH算法,性能很高, 183 * 比传统的CRC32,MD5,SHA-1 184 * (这两个算法都是加密HASH算法,复杂度本身就很高,带来的性能上的损害也不可避免) 185 * 等HASH算法要快很多,而且据说这个算法的碰撞率很低. 186 * http://murmurhash.googlepages.com/ 187 */ 188 private Long hash(String key){ 189 190 ByteBuffer buf = ByteBuffer.wrap(key.getBytes()); 191 int seed = 0x1234ABCD; 192 193 ByteOrder byteOrder = buf.order(); 194 buf.order(ByteOrder.LITTLE_ENDIAN); 195 196 long m = 0xc6a4a7935bd1e995L; 197 int r = 47; 198 199 long h = seed ^ (buf.remaining() * m); 200 201 long k; 202 while (buf.remaining() >= 8) { 203 k = buf.getLong(); 204 205 k *= m; 206 k ^= k >>> r; 207 k *= m; 208 209 h ^= k; 210 h *= m; 211 } 212 213 if (buf.remaining() > 0) { 214 ByteBuffer finish = ByteBuffer.allocate(8).order( 215 ByteOrder.LITTLE_ENDIAN); 216 // for big-endian version, do this first: 217 // finish.position(8-buf.remaining()); 218 finish.put(buf).rewind(); 219 h ^= finish.getLong(); 220 h *= m; 221 } 222 223 h ^= h >>> r; 224 h *= m; 225 h ^= h >>> r; 226 227 buf.order(byteOrder); 228 return h; 229 } 230 231 232 public static void main(String[] args) { 233 /** 234 * 客户端的hash值 和 真实主机的100个虚拟节点的hash值 235 * 236 * 一起均匀地分布在顺时针由小到大这个环上。(0 - 2^32 ) 237 * 238 * 具体 客户端 最终 连接到 哪个主机, 239 * 240 * 原则是:将客户端hash值,顺时针往后 最近的 虚拟节点hash值。 241 * 242 */ 243 Node s1 = new Node("s1", "192.168.1.1"); 244 Node s2 = new Node("s2", "192.168.1.2"); 245 Node s3 = new Node("s3", "192.168.1.3"); 246 Node s4 = new Node("s4", "192.168.1.4"); 247 Node s5 = new Node("s5", "192.168.1.5"); 248 shards.add(s1); 249 shards.add(s2); 250 shards.add(s3); 251 shards.add(s4); 252 ConsistencyHash sh = new ConsistencyHash(shards); 253 System.out.println("添加客户端,一开始有4个主机,分别为s1,s2,s3,s4,每个主机有100个虚拟主机:"); 254 for (int i = 1; i <= 9; i++) { 255 String name = "10"+i+"客户端"; 256 cliends.add(new Client(name)); 257 } 258 sh.keyToNode(cliends); 259 sh.printKeyTree(); 260 sh.deleteNode(s2); 261 sh.printKeyTree(); 262 sh.addNode(s5); 263 sh.printKeyTree(); 264 } 265 }
控制台输出结果: