一致性哈希java实现

该client采用TreeMap存储所有节点,模拟一个环形的逻辑关系。在这个环中,节点之前是存在顺序关系的,所以TreeMap的key必须实现Comparator接口。 
那节点是怎样放入这个环中的呢? 

Java代码  

      
 //对所有节点,生成nCopies个虚拟结点  
         for(Node node : nodes) {  
             //每四个虚拟结点为一组,为什么这样?下面会说到  
             for(int i=0; i<nCopies / 4; i++) {  
                 //getKeyForNode方法为这组虚拟结点得到惟一名称  
                 byte[] digest=HashAlgorithm.computeMd5(getKeyForNode(node, i));  
             /** Md5是一个16字节长度的数组,将16字节的数组每四个字节一组, 
                         分别对应一个虚拟结点,这就是为什么上面把虚拟结点四个划分一组的原因*/  
                 for(int h=0;h<4;h++) {  
                   //对于每四个字节,组成一个long值数值,做为这个虚拟节点的在环中的惟一key  
                     Long k = ((long)(digest[3+h*4]&0xFF) << 24)  
                         | ((long)(digest[2+h*4]&0xFF) << 16)  
                         | ((long)(digest[1+h*4]&0xFF) << 8)  
                         | (digest[h*4]&0xFF);  
                       
                     allNodes.put(k, node);  
                 }  
             }  
         }  



上面的流程大概可以这样归纳:四个虚拟结点为一组,以getKeyForNode方法得到这组虚拟节点的name,Md5编码后,每个虚拟结点对应Md5码16个字节中的4个,组成一个long型数值,做为这个虚拟结点在环中的惟一key。第12行k为什么是Long型的呢?呵呵,就是因为Long型实现了Comparator接口。 

处理完正式结点在环上的分布后,可以开始key在环上寻找节点的游戏了。 
对于每个key还是得完成上面的步骤:计算出Md5,根据Md5的字节数组,通过Kemata Hash算法得到key在这个环中的位置。 

Java代码  

  final Node rv;  
         byte[] digest = hashAlg.computeMd5(keyValue);  
         Long key = hashAlg.hash(digest, 0);  
         //如果找到这个节点,直接取节点,返回  
         if(!ketamaNodes.containsKey(key)) {  
         //得到大于当前key的那个子Map,然后从中取出第一个key,就是大于且离它最近的那个key  
             SortedMap<Long, Node> tailMap=ketamaNodes.tailMap(key);  
             if(tailMap.isEmpty()) {  
                 key=ketamaNodes.firstKey();  
             } else {  
                 key=tailMap.firstKey();  
             }  
             //在JDK1.6中,ceilingKey方法可以返回大于且离它最近的那个key  
             //For JDK1.6 version  
 //          key = ketamaNodes.ceilingKey(key);  
 //          if (key == null) {  
 //              key = ketamaNodes.firstKey();  
 //          }  
         }  
           
           
         rv=allNodes.get(key);  



引文中已详细描述过这种取节点逻辑:在环上顺时针查找,如果找到某个节点,就返回那个节点;如果没有找到,则取整个环的第一个节点。 

测试结果 
测试代码是自己整理的,主体方法没有变 

分布平均性测试:测试随机生成的众多key是否会平均分布到各个结点上 
测试结果如下: 

Java代码  

1. Nodes count : 5, Keys count : 100000, Normal percent : 20.0%  

2. -------------------- boundary  ----------------------  

3. Node name :node1 - Times : 20821 - Percent : 20.821001%  

4. Node name :node3 - Times : 19018 - Percent : 19.018%  

5. Node name :node5 - Times : 19726 - Percent : 19.726%  

6. Node name :node2 - Times : 19919 - Percent : 19.919%  

7. Node name :node4 - Times : 20516 - Percent : 20.516%  


最上面一行是参数说明,节点数目,总共有多少key,每个节点应该分配key的比例是多少。下面是每个结点分配到key的数目和比例。 
多次测试后发现,这个Hash算法的节点分布还是不错的,都在标准比例左右徘徊,是个合适的负载均衡算法。 


节点增删测试:在环上插入N个结点,每个节点nCopies个虚拟结点。随机生成众多key,在增删节点时,测试同一个key选择相同节点的概率 
测试如果如下: 

Java代码  

1. Normal case : nodes count : 50  

2. Added case : nodes count : 51  

3. Reduced case : nodes count : 49  

4. ------------ boundary -------------  

5. Same percent in added case : 93.765%  

6. Same percent in reduced case : 93.845%  


上面三行分别是正常情况,节点增加,节点删除情况下的节点数目。下面两行表示在节点增加和删除情况下,同一个key分配在相同节点上的比例(命中率)。 
多次测试后发现,命中率与结点数目和增减的节点数量有关。同样增删结点数目情况下,结点多时命中率高。同样节点数目,增删结点越少,命中率越高。这些都与实际情况相符。 

 

### 一致性哈希算法的 Java 实现 #### 背景概述 一致性哈希算法是一种用于分布式系统的高效负载均衡技术。它通过减少节点变化时的数据迁移量,提高了系统的稳定性和性能[^2]。 #### 核心思想 一致性哈希算法将整个哈希空间组织成一个虚拟环形结构,其中每个节点被映射到这个环上的某个位置。当需要存储或查找数据时,通过对键值计算哈希并定位到环上最近的一个顺时针方向的节点完成操作。这种设计有效减少了因节点增删而导致的大规模数据重分布问题[^3]。 以下是基于上述原理的一致性哈希算法的 Java 实现: ```java import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.*; public class ConsistentHashing<K> { private final HashFunction hashFunction; private final int numberOfReplicas; private SortedMap<Integer, K> circle = new TreeMap<>(); public ConsistentHashing(HashFunction hashFunction, int numberOfReplicas) { this.hashFunction = hashFunction; this.numberOfReplicas = numberOfReplicas; } public void addNode(K node) { for (int i = 0; i < numberOfReplicas; i++) { circle.put(hashFunction.hash(node.toString() + "-" + i), node); } } public void removeNode(K node) { for (int i = 0; i < numberOfReplicas; i++) { circle.remove(hashFunction.hash(node.toString() + "-" + i)); } } public K getNode(Object key) { if (circle.isEmpty()) { return null; } int hash = hashFunction.hash(key.toString()); Map.Entry<Integer, K> entry = circle.ceilingEntry(hash); if (entry == null) { entry = circle.firstEntry(); } return entry.getValue(); } interface HashFunction { int hash(String key); } static class Md5Hash implements HashFunction { @Override public int hash(String key) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] bytes = md5.digest(key.getBytes()); long hash = ((long) (bytes[3] & 0xFF) << 24) | ((long) (bytes[2] & 0xFF) << 16) | ((long) (bytes[1] & 0xFF) << 8) | (long) (bytes[0] & 0xFF); return Math.abs((int) hash); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } } public static void main(String[] args) { ConsistentHashing<String> consistentHashing = new ConsistentHashing<>(new Md5Hash(), 100); // 添加节点 consistentHashing.addNode("node1"); consistentHashing.addNode("node2"); consistentHashing.addNode("node3"); // 查找键对应的节点 String dataKey = "data_key"; String responsibleNode = consistentHashing.getNode(dataKey); System.out.println("The node responsible for '" + dataKey + "' is: " + responsibleNode); // 删除节点 consistentHashing.removeNode("node2"); // 再次查找 responsibleNode = consistentHashing.getNode(dataKey); System.out.println("After removing a node, the node responsible for '" + dataKey + "' is now: " + responsibleNode); } } ``` #### 关于代码解释 - **`ConsistentHashing` 类** 是核心类,负责管理一致性哈希环。 - **`addNode` 方法** 将新节点加入环中,并为其创建多个副本(即虚拟节点),以提高负载均衡效果。 - **`removeNode` 方法** 移除指定节点及其所有副本。 - **`getNode` 方法** 计算给定键的哈希值,并返回最接近该值的节点。 - 使用 `Md5Hash` 作为默认的哈希函数实现,确保生成的哈希值具有良好的分布特性[^4]。 此实现展示了如何利用一致性哈希算法在动态环境中分配资源,同时最小化数据迁移成本。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值