负载均衡-基础-一致性哈希算法及java实现

Posted 云中飞鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了负载均衡-基础-一致性哈希算法及java实现相关的知识,希望对你有一定的参考价值。

一致性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 }

控制台输出结果:

 

以上是关于负载均衡-基础-一致性哈希算法及java实现的主要内容,如果未能解决你的问题,请参考以下文章

一致性哈希算法---负载均衡

一致性哈希算法---负载均衡

springcloud负载均衡采用一致性哈希算法

一致性哈希实现负载均衡

干货 | JAVA面试题(架构篇)

一致性哈希负载均衡算法的探讨