dreamzy996

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理
  12 随笔 :: 0 文章 :: 0 评论 :: 36325 阅读

1、准备工作—hash算法

复制代码
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
 * @Describe: 实现一致性哈希算法中使用的哈希函数,
    使用MD5算法来保证一致性哈希的平衡性
 * @Author: liuyj
 * @Date: 2022-08-26 16:36
 */
public class HashFunction {
    private MessageDigest md5 = null;

    public long hash(String key) {

        if (md5 == null) {
            try {
                md5 = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException("no md5 algrithm found");
            }
        }
        md5.reset();
        md5.update(key.getBytes());
        byte[] bKey = md5.digest();

        //具体的哈希函数实现细节--每个字节 & 0xFF 再移位
        long result = ((long) (bKey[3] & 0xFF) << 24)
                | ((long) (bKey[2] & 0xFF) << 16
                | ((long) (bKey[1] & 0xFF) << 8)
                | (long) (bKey[0] & 0xFF));

        return result & 0xffffffffL;
    }
}
View Code
复制代码

2、一致性hash算法

复制代码
  1 import java.util.Collection;
  2 import java.util.HashSet;
  3 import java.util.Iterator;
  4 import java.util.Set;
  5 import java.util.SortedMap;
  6 import java.util.SortedSet;
  7 import java.util.TreeMap;
  8 import java.util.TreeSet;
  9 
 10 /**
 11  * @Describe:一致性hash函数
 12  * @Author: liuyj
 13  * @Date: 2022-08-26 16:40
 14  */
 15 public class ConsistentHash<T> {
 16     private final HashFunction hashFunction;
 17     private final int numberOfReplicas;
 18 
 19     // 节点的复制因子,实际节点个数 * numberOfReplicas =虚拟节点个数
 20     private final SortedMap<Long,Object> circle = new TreeMap<>();
 21 
 22     // 存储虚拟节点的hash值到真实节点的映射
 23     public ConsistentHash(HashFunction hashFunction, int numberOfReplicas,
 24                           Collection<T> nodes){
 25         this.hashFunction = hashFunction;
 26         this.numberOfReplicas = numberOfReplicas;
 27         for (T node : nodes)
 28             add(node);
 29     }
 30 
 31     public void add(T node){
 32         // 对于一个实际机器节点 node, 对应 numberOfReplicas 个虚拟节点
 33         for (int i = 0; i < numberOfReplicas; i++){
 34             /*
 35              * 不同的虚拟节点(i不同)有不同的hash值,但都对应同一个实际机器node
 36              * 虚拟node一般是均衡分布在环上的,数据存储在顺时针方向的虚拟node上
 37              */
 38             circle.put(hashFunction.hash(node.toString() + i), node);
 39         }
 40     }
 41 
 42     public void remove(T node){
 43         for (int i = 0; i < numberOfReplicas; i++)
 44             circle.remove(hashFunction.hash(node.toString() + i));
 45     }
 46 
 47     /**
 48      * 获得一个最近的顺时针节点,根据给定的key 取Hash
 49      * 然后再取得顺时针方向上最近的一个虚拟节点对应的实际节点
 50      * 再从实际节点中取得 数据
 51      */
 52     public T get(Object key){
 53         if (circle.isEmpty()){
 54             return null;
 55         }
 56         long hash = hashFunction.hash((String) key);
 57 
 58         // node 用String来表示,获得node在哈希环中的hashCode
 59         if (!circle.containsKey(hash)) {
 60             //数据映射在两台虚拟机器所在环之间,就需要按顺时针方向寻找机器
 61             SortedMap tailMap = circle.tailMap(hash);
 62             hash = tailMap.isEmpty() ? circle.firstKey() : (long) tailMap.firstKey();
 63         }
 64         return (T) circle.get(hash);
 65     }
 66 
 67     public long getSize(){
 68         return circle.size();
 69     }
 70 
 71     // 查看MD5算法生成的hashCode值---表示整个哈希环中各个虚拟节点位置
 72     public void testBalance(){
 73 
 74         Set sets  = circle.keySet();
 75         //获得TreeMap中所有的Key
 76         SortedSet<Long> sortedSets= new TreeSet(sets);
 77         //将获得的Key集合排序
 78         for(Long hashCode : sortedSets){
 79             System.out.println(hashCode);
 80         }
 81         System.out.println("----each location 's distance are follows: ----");
 82 
 83         // 查看用MD5算法生成的long hashCode 相邻两个hashCode的差值
 84         Iterator it = sortedSets.iterator();
 85         Iterator it2 = sortedSets.iterator();
 86         if(it2.hasNext()){
 87             it2.next();
 88         }
 89 
 90         long keyPre, keyAfter;
 91         while(it.hasNext() && it2.hasNext()){
 92             keyPre = (long) it.next();
 93             keyAfter = (long) it2.next();
 94             System.out.println(keyAfter - keyPre);
 95         }
 96     }
 97 
 98     public static void main(String[] args){
 99         Set<String> nodes = new HashSet<>();
100         nodes.add("A");
101         nodes.add("B");
102         nodes.add("C");
103         ConsistentHash consistentHash = new ConsistentHash(new HashFunction(), 2, nodes);
104         consistentHash.add("D");
105         System.out.println("hash circle size: " + consistentHash.getSize());
106         System.out.println("location of each node are follows: ");
107         consistentHash.testBalance();
108     }
109 }
View Code
复制代码

3、具体应用—解决数据迁移问题讲解

3.1如何解决集群中添加或者删除机器上需要迁移大量数据的问题?

假设采用传统的哈希取模法,设有K台物理机,H(key)=hash(key) mod K 即可实现数据分片。但当K变化时(如新增一台机器),所有已经映射好的数据都需要重新再映射。H(key)=hash(key) mod (K+1)。

一致性哈希采用的做法如下:

  • 引入一个环的概念。
  • 先将机器映射到这个环上,再将数据也通过相同的哈希函数映射到这个环上,数据存储在它顺时针走向的那台机器上。
  • 以环为中介,实现了数据与机器数目之间的解藕。
  • 这样,当机器的数目变化时,只会影响到增加或删除的那台机器所在的环的邻接机器的数据存储,而其他机器上的数据不受影响。

在具体JAVA实现代码中,定义了一个TreeMap用来保存虚拟机器节点到实际的物理机器的映射。机器以字符串形式来标识,故hash函数的参数为String

1
2
3
4
5
6
7
8
// 对于一个实际机器节点 node, 对应 numberOfReplicas 个虚拟节点
for (int i = 0; i < numberOfReplicas; i++){
    /*
    * 不同的虚拟节点(i不同)有不同的hash值,但都对应同一个实际机器node
    * 虚拟node一般是均衡分布在环上的,数据存储在顺时针方向的虚拟node上
    */
    circle.put(hashFunction.hash(node.toString() + i), node);
}

而对于 数据的存储而言,逻辑上是按顺时针方向存储在虚拟机器节点中,虚拟机器节点通过TreeMap知道它实际需要将数据存储在哪台物理机器上。此外,TreeMap中的Key是有序的,而环也是顺时针有序的,这样才能当数据被映射到两台虚拟机器之间的弧上时,通过TreeMap的 tailMap()来寻找顺时针方向上的下一台虚拟机。

1
2
3
4
5
if (!circle.containsKey(hash)) {
//数据映射在两台虚拟机器所在环之间,就需要按顺时针方向寻找机器
SortedMap tailMap = circle.tailMap(hash);
hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
}

  

posted on   凉小枫  阅读(35)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示