【策略】一致性Hash演算法(Hash環)的java程式碼實現

Love Lenka發表於2017-05-16

【一】一致性hash演算法,基本實現分佈平衡。

 1 package org.ehking.quartz.curator;
 2 
 3 import java.util.SortedMap;
 4 import java.util.TreeMap;
 5 
 6 public class ConsistentHashingWithoutVirtualNode {
 7     /**
 8            * 待新增入Hash環的伺服器列表
 9           */
10          private static String[] servers = {"192.168.0.0:111", "192.168.0.1:111", "192.168.0.2:111",
11                 "192.168.0.3:111", "192.168.0.4:111"};
12      
13        /**
14       * key表示伺服器的hash值,value表示伺服器的名稱
15          */
16          private static SortedMap<Integer, String> sortedMap = 
17              new TreeMap<Integer, String>();
18         
19      /**
20       * 程式初始化,將所有的伺服器放入sortedMap中
21      */
22      static
23   {
24       for (int i = 0; i < servers.length; i++)
25        {
26           int hash = getHash(servers[i]);
27           System.out.println("[" + servers[i] + "]加入集合中, 其Hash值為" + hash);
28            sortedMap.put(hash, servers[i]);
29        }
30         System.out.println();
31     }
32      
33      /**
34       * 使用FNV1_32_HASH演算法計算伺服器的Hash值,這裡不使用重寫hashCode的方法,最終效果沒區別 
35      */
36      private static int getHash(String str)
37      {
38          final int p = 16777619;
39          int hash = (int)2166136261L;
40          for (int i = 0; i < str.length(); i++)
41         hash = (hash ^ str.charAt(i)) * p;
42         hash += hash << 13;
43        hash ^= hash >> 7;
44        hash += hash << 3;
45       hash ^= hash >> 17;
46         hash += hash << 5;
47          
48         // 如果算出來的值為負數則取其絕對值
49         if (hash < 0)
50             hash = Math.abs(hash);
51          return hash;
52      }
53    
54      /**
55       * 得到應當路由到的結點
56       */
57      private static String getServer(String node)
58      {
59          // 得到帶路由的結點的Hash值
60          int hash = getHash(node);
61         // 得到大於該Hash值的所有Map
62          SortedMap<Integer, String> subMap = 
63                  sortedMap.tailMap(hash);
64          // 第一個Key就是順時針過去離node最近的那個結點
65         Integer i = subMap.firstKey();
66          // 返回對應的伺服器名稱
67        return subMap.get(i);
68      }
69     
70      public static void main(String[] args)
71     {
72          String[] nodes = {"127.0.0.1:1111", "221.226.0.1:2222", "10.211.0.1:3333"};
73        for (int i = 0; i < nodes.length; i++)
74            System.out.println("[" + nodes[i] + "]的hash值為" + 
75                     getHash(nodes[i]) + ", 被路由到結點[" + getServer(nodes[i]) + "]");
76     }
77 }
View Code

【二】藉助虛擬節點,實現分佈平衡的hash演算法

  1 package org.ehking.quartz.curator;
  2 
  3 import java.util.ArrayList;
  4 import java.util.HashMap;
  5 import java.util.LinkedList;
  6 import java.util.List;
  7 import java.util.Set;
  8 import java.util.SortedMap;
  9 import java.util.TreeMap;
 10 import java.util.UUID;
 11 
 12 public class ConsistentHashingWithVirtualNode {
 13     /**
 14      * 待新增入Hash環的伺服器列表
 15       */
 16     private static String[] servers = { "192.168.0.0:111","192.168.0.1:111", "192.168.0.2:111"};
 17    
 18    /**
 19       * 真實結點列表,考慮到伺服器上線、下線的場景,即新增、刪除的場景會比較頻繁,這裡使用LinkedList會更好
 20      */
 21    private static List<String> realNodes = new LinkedList<String>();
 22    /**
 23     * 虛擬節點,key表示虛擬節點的hash值,value表示虛擬節點的名稱
 24     */
 25    private static SortedMap<Integer, String> virtualNodes = 
 26            new TreeMap<Integer, String>();
 27     
 28     /**
 29       * 虛擬節點的數目,這裡寫死,為了演示需要,一個真實結點對應5個虛擬節點
 30     */
 31      private static final int VIRTUAL_NODES = 1000;
 32    
 33   static
 34   {
 35         // 先把原始的伺服器新增到真實結點列表中
 36          for (int i = 0; i < servers.length; i++)
 37              realNodes.add(servers[i]);
 38          
 39         // 再新增虛擬節點,遍歷LinkedList使用foreach迴圈效率會比較高
 40          for (String str : realNodes)
 41          {
 42              for (int i = 0; i < VIRTUAL_NODES; i++)
 43              {
 44                String virtualNodeName = str + "&&VN" + String.valueOf(i);
 45                  int hash = getHash(virtualNodeName);
 46                  System.out.println("虛擬節點[" + virtualNodeName + "]被新增, hash值為" + hash);
 47                 virtualNodes.put(hash, virtualNodeName);
 48             }
 49          }
 50          System.out.println();
 51    }
 52    
 53      /**
 54       * 使用FNV1_32_HASH演算法計算伺服器的Hash值,這裡不使用重寫hashCode的方法,最終效果沒區別 
 55       */
 56      private static int getHash(String str)
 57      {
 58          final int p = 16777619;
 59         int hash = (int)2166136261L;
 60         for (int i = 0; i < str.length(); i++)
 61             hash = (hash ^ str.charAt(i)) * p;
 62           hash += hash << 13;
 63         hash ^= hash >> 7;
 64         hash += hash << 3;
 65         hash ^= hash >> 17;
 66         hash += hash << 5;
 67        
 68         // 如果算出來的值為負數則取其絕對值
 69          if (hash < 0)
 70              hash = Math.abs(hash);
 71          return hash;
 72      }
 73     
 74      /**
 75       * 得到應當路由到的結點
 76       */
 77      private static String getServer(String node)
 78      {
 79          // 得到帶路由的結點的Hash值
 80          int hash = getHash(node);
 81          // 得到大於該Hash值的所有Map
 82          SortedMap<Integer, String> subMap = 
 83                  virtualNodes.tailMap(hash);
 84          Integer i=null;
 85          String virtualNode = null;
 86          if(subMap==null||subMap.size()==0){
 87              i=virtualNodes.firstKey();
 88              virtualNode=virtualNodes.get(i);
 89          }else{
 90               i = subMap.firstKey();
 91               virtualNode= subMap.get(i);
 92          }
 93          // 第一個Key就是順時針過去離node最近的那個結點
 94        
 95          // 返回對應的虛擬節點名稱,這裡字串稍微擷取一下
 96          return virtualNode.substring(0, virtualNode.indexOf("&&"));
 97      }
 98      
 99      public static void main(String[] args)
100      {
101         
102         HashMap<String,Integer> map=new HashMap<String, Integer>(); 
103          List<String> id=new ArrayList<String>();
104          for(int i=0;i<1000;i++){
105              id.add(UUID.randomUUID().toString().replace("-", ""));
106              //id.add("adasfdsafdsgfdsagdsafdsafdsaf"+i);
107          }         
108          for (int i = 0; i < id.size(); i++){
109              String aString=getServer(id.get(i));
110              Integer aInteger=map.get(aString);
111              if(aInteger==null){
112                  map.put(aString,1);
113              }else{
114                  map.put(aString, aInteger+1);
115              }
116          }
117          Set<String> set= map.keySet();
118         for(String a:set){
119             System.out.println("節點【"+a+"】分配到元素個數為==>"+map.get(a));
120         }
121     }
122  }
View Code

 

相關文章