• 博客园logo
  • 会员
  • 周边
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

无信不立

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【策略】一致性Hash算法(Hash环)的java代码实现

【一】一致性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

 【三】一致性hash算法工具类

import lombok.Data;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;



public class ConsistentHashingWithVirtualNodeUtils {
    /**
     * hash环的缓存
     * key:真实分片的个数
     * value:hash环缓存信息
     */
    private static final Map<String, VirtualNodeCache> HASH_RING_CACHE = new ConcurrentHashMap<>();

    private static final String VIRTUAL_SPLIT = "&V";
    /**
     * 一个hash环允许的节点数最大为10000
     */
    private static final Integer MAX_NODE =10000;


    /**
     * @param hashTarget   参与一致性hash的目标
     * @param sumShardNode 总的分片数
     * @return
     */
    public static int getShardItem(String hashTarget, int sumShardNode) {
        String cacheKey = String.valueOf(sumShardNode);
        VirtualNodeCache virtualNodeCache = HASH_RING_CACHE.get(cacheKey);
        if (virtualNodeCache == null) {
            virtualNodeCache = buildVirtualNodeCache(sumShardNode);
            VirtualNodeCache tryPutData = HASH_RING_CACHE.putIfAbsent(cacheKey, virtualNodeCache);
            virtualNodeCache = tryPutData != null ? tryPutData : virtualNodeCache;
        }
        return selectShardItem(hashTarget, virtualNodeCache);
    }

    /**
     *
     * @param sumShardNode
     * @return
     */
    private static VirtualNodeCache buildVirtualNodeCache(int sumShardNode) {
        VirtualNodeCache virtualNodeCache = new VirtualNodeCache();
        boolean haveVirtualNode = false;
        TreeMap<Integer, String> hashRing = new TreeMap<>();
        if (sumShardNode >= 10000) {
            for (int i = 1; i <= sumShardNode; i++) {
                String value = String.valueOf(i);
                hashRing.put(getHash(value), value);
            }
        } else {
            haveVirtualNode = true;
            int virNode = 10000 / sumShardNode;
            if (virNode == 0) {
                virNode = 1;
            }
            for (int i = 1; i <= sumShardNode; i++) {
                for (int j = 0; j < virNode; j++) {
                    String value = i + VIRTUAL_SPLIT + j;
                    hashRing.put(getHash(value), value);
                }
            }
        }
        virtualNodeCache.setHashRing(hashRing);
        virtualNodeCache.setHaveVirtualNode(haveVirtualNode);
        return virtualNodeCache;
    }


    private static int selectShardItem(String hashTarget, VirtualNodeCache virtualNodeCache) {
        int hash = getHash(hashTarget);
        TreeMap<Integer, String> treeMap = virtualNodeCache.getHashRing();
        Map.Entry<Integer, String> result = treeMap.ceilingEntry(hash);
        if (result == null) {
            result = treeMap.firstEntry();
        }
        String vNodeStr = result.getValue();
        return virtualNodeCache.isHaveVirtualNode() ? Integer.parseInt(vNodeStr.substring(0, vNodeStr.indexOf(VIRTUAL_SPLIT))) : Integer.parseInt(vNodeStr);
    }


    /**
     * hash算法
     *
     * @param str
     * @return
     */
    private static int getHash(String str) {
        final int p = 16777619;
        int hash = (int) 2166136261L;
        for (int i = 0; i < str.length(); i++)
            hash = (hash ^ str.charAt(i)) * p;
        hash += hash << 13;
        hash ^= hash >> 7;
        hash += hash << 3;
        hash ^= hash >> 17;
        hash += hash << 5;
        // 如果算出来的值为负数则取其绝对值
        if (hash < 0) hash = Math.abs(hash);
        return hash;
    }


    @Data
    private static class VirtualNodeCache {
        //是否有虚拟节点
        private boolean haveVirtualNode;
        //虚拟节点hash环
        //key:虚拟节点的hash值
        //value:虚拟节点的字符串
        private TreeMap<Integer, String> hashRing;
    }


    public static void main(String[] args) {
        Map<Integer,Integer> map = new HashMap<>();

        for(int i=0;i<30000;i++){
           int shard = ConsistentHashingWithVirtualNodeUtils.getShardItem(String.valueOf(i),10);
           Integer integer =  map.get(shard);
           if(integer ==null){
               integer=1;
           }else{
               integer++;
           }
           map.put(shard,integer);
        }

        System.out.println(map.entrySet());
    }
}
View Code

 

posted on 2017-05-16 13:55  无信不立  阅读(3044)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3