├── .gitignore ├── src └── com │ └── github │ └── jaskey │ └── consistenthash │ ├── HashFunction.java │ ├── Node.java │ ├── VirtualNode.java │ ├── sample │ ├── MyServiceNode.java │ └── DistributionTestSample.java │ └── ConsistentHashRouter.java └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### Eclipse ### 3 | .settings/ 4 | .project 5 | 6 | 7 | ### Intellij ### 8 | 9 | .idea/ 10 | /out/ 11 | 12 | # mpeltonen/sbt-idea plugin 13 | .idea_modules/ 14 | *.iml 15 | 16 | ### Java ### 17 | # Compiled class file 18 | *.class 19 | 20 | # Log file 21 | *.log 22 | 23 | 24 | # Package Files # 25 | *.jar 26 | *.war 27 | *.ear 28 | *.zip 29 | *.tar.gz 30 | *.rar -------------------------------------------------------------------------------- /src/com/github/jaskey/consistenthash/HashFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.jaskey.consistenthash; 18 | 19 | /** 20 | * @author linjunjie1103@gmail.com 21 | * 22 | * Hash String to long value 23 | */ 24 | public interface HashFunction { 25 | long hash(String key); 26 | } 27 | -------------------------------------------------------------------------------- /src/com/github/jaskey/consistenthash/Node.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.jaskey.consistenthash; 18 | 19 | /** 20 | * @author linjunjie1103@gmail.com 21 | * Represent a node which should be mapped to a hash ring 22 | */ 23 | public interface Node { 24 | /** 25 | * 26 | * @return the key which will be used for hash mapping 27 | */ 28 | String getKey(); 29 | } 30 | -------------------------------------------------------------------------------- /src/com/github/jaskey/consistenthash/VirtualNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.jaskey.consistenthash; 18 | 19 | /** 20 | * @author linjunjie1103@gmail.com 21 | * @param 22 | */ 23 | public class VirtualNode implements Node { 24 | final T physicalNode; 25 | final int replicaIndex; 26 | 27 | public VirtualNode(T physicalNode, int replicaIndex) { 28 | this.replicaIndex = replicaIndex; 29 | this.physicalNode = physicalNode; 30 | } 31 | 32 | @Override 33 | public String getKey() { 34 | return physicalNode.getKey() + "-" + replicaIndex; 35 | } 36 | 37 | public boolean isVirtualNodeOf(T pNode) { 38 | return physicalNode.getKey().equals(pNode.getKey()); 39 | } 40 | 41 | public T getPhysicalNode() { 42 | return physicalNode; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Consistent Hash Implementation in Java 2 | 3 | Reference http://www.codeproject.com/Articles/56138/Consistent-hashing 4 | 5 | ## Get Started 6 | 7 | //initialize 4 service node 8 | MyServiceNode node1 = new MyServiceNode("IDC1","127.0.0.1",8080); 9 | MyServiceNode node2 = new MyServiceNode("IDC1","127.0.0.1",8081); 10 | MyServiceNode node3 = new MyServiceNode("IDC1","127.0.0.1",8082); 11 | MyServiceNode node4 = new MyServiceNode("IDC1","127.0.0.1",8084); 12 | 13 | //hash them to hash ring 14 | ConsistentHashRouter consistentHashRouter = new ConsistentHashRouter<>(Arrays.asList(node1,node2,node3,node4),10);//10 virtual nodes 15 | 16 | String requestIp = "192.168.0.1"; 17 | System.out.println(requestIp + " is route to " + consistentHashRouter.routeNode(requestIp)); 18 | 19 | Please see `MyServiceNode.java` for more usage details 20 | 21 | 22 | 23 | ## Developer Doc 24 | 25 | ### Node 26 | 27 | Any class that implements `Node` can be mapped to `ConsistentHashRouter`. 28 | 29 | ### VirtualNode 30 | 31 | Your custom `Node` represents a real physical node, which supports numbers of virtual nodes , the replicas of physical node. 32 | 33 | When adding new `Node` to the `ConsistentHashRouter`, you can specify how many virtual nodes should be replicated. 34 | 35 | ### HashFunction 36 | 37 | By default , `ConsistentHashRouter` will use MD5 to hash a node, you may specify your custom hash function by implementing `HashFunction` 38 | 39 | 40 | ## Contact Author 41 | 42 | For any question or feature required, please post a Github issue directly. 43 | 44 | To contact author, please mail to linjunjie1103@gmail.com 45 | 46 | ---------------------------------------------------------------------------------------------- 47 | 48 | # Java 实现的一致性哈希 49 | 50 | 原理参考 http://www.codeproject.com/Articles/56138/Consistent-hashing 51 | 52 | ## 快速开始 53 | 54 | //初始化4个服务节点 55 | MyServiceNode node1 = new MyServiceNode("IDC1","127.0.0.1",8080); 56 | MyServiceNode node2 = new MyServiceNode("IDC1","127.0.0.1",8081); 57 | MyServiceNode node3 = new MyServiceNode("IDC1","127.0.0.1",8082); 58 | MyServiceNode node4 = new MyServiceNode("IDC1","127.0.0.1",8084); 59 | 60 | //把节点哈希到哈希环中 61 | ConsistentHashRouter consistentHashRouter = new ConsistentHashRouter<>(Arrays.asList(node1,node2,node3,node4),10);//10个虚拟节点 62 | 63 | String requestIp = "192.168.0.1"; 64 | System.out.println(requestIp + " is route to " + consistentHashRouter.routeNode(requestIp)); 65 | 66 | 更多的使用的实例请参看: `MyServiceNode.java` 67 | 68 | 69 | 70 | ## 开发者文档 71 | 72 | ### Node(节点) 73 | 74 | 任意一个实现了`Node`接口的类的对象都可以映射到`ConsistentHashRouter`中 75 | 76 | ### VirtualNode(虚拟节点) 77 | 78 | 自定义的 `Node`代表一个物理的节点,这些物理节点支持指定一些虚拟节点作为物理节点的复制(replicas)。当需要增加`Node` 到 `ConsistentHashRouter`时,你可以指定这个节点需要复制有多少个虚拟节点。 79 | 80 | 81 | ### HashFunction(哈希函数) 82 | 83 | 默认情况下, `ConsistentHashRouter` 会使用MD5去哈希一个节点,你也可以通过去实现`HashFunction`指定自己的自定义哈希函数 84 | 85 | 86 | 87 | ## 联系作者 88 | 89 | - 需要新功能支持或有问题反馈请提ISSUE 90 | - 作者邮箱:linjunjie1103@gmail.com 91 | -------------------------------------------------------------------------------- /src/com/github/jaskey/consistenthash/sample/MyServiceNode.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.jaskey.consistenthash.sample; 18 | 19 | import com.github.jaskey.consistenthash.ConsistentHashRouter; 20 | import com.github.jaskey.consistenthash.Node; 21 | import java.util.Arrays; 22 | 23 | /** 24 | * a sample usage for routing a request to services based on requester ip 25 | */ 26 | public class MyServiceNode implements Node{ 27 | private final String idc; 28 | private final String ip; 29 | private final int port; 30 | 31 | public MyServiceNode(String idc,String ip, int port) { 32 | this.idc = idc; 33 | this.ip = ip; 34 | this.port = port; 35 | } 36 | 37 | @Override 38 | public String getKey() { 39 | return idc + "-"+ip+":"+port; 40 | } 41 | 42 | @Override 43 | public String toString(){ 44 | return getKey(); 45 | } 46 | 47 | public static void main(String[] args) { 48 | //initialize 4 service node 49 | MyServiceNode node1 = new MyServiceNode("IDC1","127.0.0.1",8080); 50 | MyServiceNode node2 = new MyServiceNode("IDC1","127.0.0.1",8081); 51 | MyServiceNode node3 = new MyServiceNode("IDC1","127.0.0.1",8082); 52 | MyServiceNode node4 = new MyServiceNode("IDC1","127.0.0.1",8084); 53 | 54 | //hash them to hash ring 55 | ConsistentHashRouter consistentHashRouter = new ConsistentHashRouter<>(Arrays.asList(node1,node2,node3,node4),10);//10 virtual node 56 | 57 | //we have 5 requester ip, we are trying them to route to one service node 58 | String requestIP1 = "192.168.0.1"; 59 | String requestIP2 = "192.168.0.2"; 60 | String requestIP3 = "192.168.0.3"; 61 | String requestIP4 = "192.168.0.4"; 62 | String requestIP5 = "192.168.0.5"; 63 | 64 | goRoute(consistentHashRouter,requestIP1,requestIP2,requestIP3,requestIP4,requestIP5); 65 | 66 | MyServiceNode node5 = new MyServiceNode("IDC2","127.0.0.1",8080);//put new service online 67 | System.out.println("-------------putting new node online " +node5.getKey()+"------------"); 68 | consistentHashRouter.addNode(node5,10); 69 | 70 | goRoute(consistentHashRouter,requestIP1,requestIP2,requestIP3,requestIP4,requestIP5); 71 | 72 | consistentHashRouter.removeNode(node3); 73 | System.out.println("-------------remove node online " + node3.getKey() + "------------"); 74 | goRoute(consistentHashRouter,requestIP1,requestIP2,requestIP3,requestIP4,requestIP5); 75 | 76 | 77 | } 78 | 79 | private static void goRoute(ConsistentHashRouter consistentHashRouter ,String ... requestIps){ 80 | for (String requestIp: requestIps) { 81 | System.out.println(requestIp + " is route to " + consistentHashRouter.routeNode(requestIp)); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/com/github/jaskey/consistenthash/sample/DistributionTestSample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.jaskey.consistenthash.sample; 18 | 19 | import com.github.jaskey.consistenthash.ConsistentHashRouter; 20 | import com.github.jaskey.consistenthash.Node; 21 | 22 | import java.util.*; 23 | import java.util.concurrent.atomic.AtomicInteger; 24 | 25 | /** 26 | * a test sample to test the hash function routing distribution, by default ConsistentHashRouter will use a inner MD5 hash algorithm 27 | */ 28 | public class DistributionTestSample{ 29 | 30 | public static void main(String[] args) { 31 | //initialize 4 service node 32 | MyServiceNode node1 = new MyServiceNode("IDC1", "10.8.1.11", 8080); 33 | MyServiceNode node2 = new MyServiceNode("IDC1", "10.8.3.99", 8080); 34 | MyServiceNode node3 = new MyServiceNode("IDC1", "10.9.11.105", 8080); 35 | MyServiceNode node4 = new MyServiceNode("IDC1", "10.10.9.210", 8080); 36 | 37 | //hash them to hash ring. 38 | // 1. By default a MD5 hash function will be used, you can modify a little if you want to test your own hash funtion 39 | // 2. Another factor which is will influence distribution is the numbers of virtual nodes, you can change this factor , below, we use 20 virtual nodes for each physical node. 40 | ConsistentHashRouter consistentHashRouter = new ConsistentHashRouter<>(Arrays.asList(node1,node2,node3,node4),20);//20 virtual node 41 | 42 | List requestIps = new ArrayList<>(); 43 | for(int i = 0; i < 100000; i++) { 44 | requestIps.add(getRandomIp()); 45 | } 46 | 47 | 48 | System.out.println("==========output distribution result=========="); 49 | System.out.println(goRoute(consistentHashRouter, requestIps.toArray(new String[requestIps.size()])).toString()); 50 | 51 | 52 | } 53 | 54 | private static TreeMap goRoute(ConsistentHashRouter consistentHashRouter , String ... requestIps){ 55 | TreeMap res = new TreeMap<>(); 56 | for (String requestIp: requestIps) { 57 | MyServiceNode mynode = consistentHashRouter.routeNode(requestIp); 58 | res.putIfAbsent(mynode.getKey(), new AtomicInteger()); 59 | res.get(mynode.getKey()).incrementAndGet(); 60 | System.out.println(requestIp + " is routed to " + mynode); 61 | } 62 | return res; 63 | } 64 | 65 | private static String getRandomIp() { 66 | int[][] range = {{607649792, 608174079},// 36.56.0.0-36.63.255.255 67 | {1038614528, 1039007743},// 61.232.0.0-61.237.255.255 68 | {1783627776, 1784676351},// 106.80.0.0-106.95.255.255 69 | {2035023872, 2035154943},// 121.76.0.0-121.77.255.255 70 | {2078801920, 2079064063},// 123.232.0.0-123.235.255.255 71 | {-1950089216, -1948778497},// 139.196.0.0-139.215.255.255 72 | {-1425539072, -1425014785},// 171.8.0.0-171.15.255.255 73 | {-1236271104, -1235419137},// 182.80.0.0-182.92.255.255 74 | {-770113536, -768606209},// 210.25.0.0-210.47.255.255 75 | {-569376768, -564133889}, // 222.16.0.0-222.95.255.255 76 | }; 77 | 78 | Random rdint = new Random(); 79 | int index = rdint.nextInt(10); 80 | String ip = num2ip(range[index][0] + new Random().nextInt(range[index][1] - range[index][0])); 81 | return ip; 82 | } 83 | 84 | private static String num2ip(int ip) { 85 | int[] b = new int[4]; 86 | String x = ""; 87 | 88 | b[0] = (int) ((ip >> 24) & 0xff); 89 | b[1] = (int) ((ip >> 16) & 0xff); 90 | b[2] = (int) ((ip >> 8) & 0xff); 91 | b[3] = (int) (ip & 0xff); 92 | x = Integer.toString(b[0]) + "." + Integer.toString(b[1]) + "." + Integer.toString(b[2]) + "." + Integer.toString(b[3]); 93 | 94 | return x; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/com/github/jaskey/consistenthash/ConsistentHashRouter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.github.jaskey.consistenthash; 18 | 19 | import java.security.MessageDigest; 20 | import java.security.NoSuchAlgorithmException; 21 | import java.util.Collection; 22 | import java.util.Iterator; 23 | import java.util.SortedMap; 24 | import java.util.TreeMap; 25 | 26 | /** 27 | * @author linjunjie1103@gmail.com 28 | * 29 | * To hash Node objects to a hash ring with a certain amount of virtual node. 30 | * Method routeNode will return a Node instance which the object key should be allocated to according to consistent hash algorithm 31 | * 32 | * @param 33 | */ 34 | public class ConsistentHashRouter { 35 | private final SortedMap> ring = new TreeMap<>(); 36 | private final HashFunction hashFunction; 37 | 38 | public ConsistentHashRouter(Collection pNodes, int vNodeCount) { 39 | this(pNodes,vNodeCount, new MD5Hash()); 40 | } 41 | 42 | /** 43 | * 44 | * @param pNodes collections of physical nodes 45 | * @param vNodeCount amounts of virtual nodes 46 | * @param hashFunction hash Function to hash Node instances 47 | */ 48 | public ConsistentHashRouter(Collection pNodes, int vNodeCount, HashFunction hashFunction) { 49 | if (hashFunction == null) { 50 | throw new NullPointerException("Hash Function is null"); 51 | } 52 | this.hashFunction = hashFunction; 53 | if (pNodes != null) { 54 | for (T pNode : pNodes) { 55 | addNode(pNode, vNodeCount); 56 | } 57 | } 58 | } 59 | 60 | /** 61 | * add physic node to the hash ring with some virtual nodes 62 | * @param pNode physical node needs added to hash ring 63 | * @param vNodeCount the number of virtual node of the physical node. Value should be greater than or equals to 0 64 | */ 65 | public void addNode(T pNode, int vNodeCount) { 66 | if (vNodeCount < 0) throw new IllegalArgumentException("illegal virtual node counts :" + vNodeCount); 67 | int existingReplicas = getExistingReplicas(pNode); 68 | for (int i = 0; i < vNodeCount; i++) { 69 | VirtualNode vNode = new VirtualNode<>(pNode, i + existingReplicas); 70 | ring.put(hashFunction.hash(vNode.getKey()), vNode); 71 | } 72 | } 73 | 74 | /** 75 | * remove the physical node from the hash ring 76 | * @param pNode 77 | */ 78 | public void removeNode(T pNode) { 79 | Iterator it = ring.keySet().iterator(); 80 | while (it.hasNext()) { 81 | Long key = it.next(); 82 | VirtualNode virtualNode = ring.get(key); 83 | if (virtualNode.isVirtualNodeOf(pNode)) { 84 | it.remove(); 85 | } 86 | } 87 | } 88 | 89 | /** 90 | * with a specified key, route the nearest Node instance in the current hash ring 91 | * @param objectKey the object key to find a nearest Node 92 | * @return 93 | */ 94 | public T routeNode(String objectKey) { 95 | if (ring.isEmpty()) { 96 | return null; 97 | } 98 | Long hashVal = hashFunction.hash(objectKey); 99 | SortedMap> tailMap = ring.tailMap(hashVal); 100 | Long nodeHashVal = !tailMap.isEmpty() ? tailMap.firstKey() : ring.firstKey(); 101 | return ring.get(nodeHashVal).getPhysicalNode(); 102 | } 103 | 104 | 105 | public int getExistingReplicas(T pNode) { 106 | int replicas = 0; 107 | for (VirtualNode vNode : ring.values()) { 108 | if (vNode.isVirtualNodeOf(pNode)) { 109 | replicas++; 110 | } 111 | } 112 | return replicas; 113 | } 114 | 115 | 116 | //default hash function 117 | private static class MD5Hash implements HashFunction { 118 | MessageDigest instance; 119 | 120 | public MD5Hash() { 121 | try { 122 | instance = MessageDigest.getInstance("MD5"); 123 | } catch (NoSuchAlgorithmException e) { 124 | } 125 | } 126 | 127 | @Override 128 | public long hash(String key) { 129 | instance.reset(); 130 | instance.update(key.getBytes()); 131 | byte[] digest = instance.digest(); 132 | 133 | long h = 0; 134 | for (int i = 0; i < 4; i++) { 135 | h <<= 8; 136 | h |= ((int) digest[i]) & 0xFF; 137 | } 138 | return h; 139 | } 140 | } 141 | 142 | } 143 | --------------------------------------------------------------------------------