一致性哈希算法学习及JAVA代码实现分析

简介:

1,对于待存储的海量数据,如何将它们分配到各个机器中去?---数据分片与路由

当数据量很大时,通过改善单机硬件资源的纵向扩充方式来存储数据变得越来越不适用,而通过增加机器数目来获得水平横向扩展的方式则越来越流行。因此,就有个问题,如何将这些海量的数据分配到各个机器中?数据分布到各个机器存储之后,又如何进行查找?这里主要记录一致性Hash算法如何将数据分配到各个机器中去。

 

2,衡量一致性哈希算法好处的四个标准:

①平衡性:平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。②单调性:单调性是指如果已经有一些数据通过哈希分配到了相应的机器上,又有新的机器加入到系统中。哈希的结果应能够保证原有已分配的内容可以被映射到原有的或者新的机器中去,而不会被映射到旧的机器集合中的其他机器上。

这里再解释一下:就是原有的数据要么还是呆在它所在的机器上不动,要么被迁移到新的机器上,而不会迁移到旧的其他机器上。

③分散性④负载参考这里

 

3,一致性哈希的原理:

由于一般的哈希函数返回一个int(32bit)型的hashCode。因此,可以将该哈希函数能够返回的hashCode表示成一个范围为0---(2^32)-1 环。

将机器的标识(如:IP地址)作为哈希函数的Key映射到环上。如:

hash(Node1) =Key1      hash(Node2) = Key2,借用一张图如下:

 

 

 同样,数据也通过相同的哈希函数映射到环上。这样,按照顺时针方向,数据存放在它所在的顺时针方向上的那个机器上。这就是一致性哈希算法分配数据的方式!

 

4,JAVA实现一致性哈希算法的代码分析:

设计哈希函数

这里采用了MD5算法,主要是用来保证平衡性,即能够将机器均衡地映射到环上。貌似用Jdk中String类的hashCode并不能很好的保证平衡性。

复制代码
 1 import java.security.MessageDigest;
 2 import java.security.NoSuchAlgorithmException;
 3 
 4 /*
 5  * 实现一致性哈希算法中使用的哈希函数,使用MD5算法来保证一致性哈希的平衡性
 6  */
 7 public class HashFunction {
 8     private MessageDigest md5 = null;
 9 
10     public long hash(String key) {
11         if (md5 == null) {
12             try {
13                 md5 = MessageDigest.getInstance("MD5");
14             } catch (NoSuchAlgorithmException e) {
15                 throw new IllegalStateException("no md5 algrithm found");
16             }
17         }
18 
19         md5.reset();
20         md5.update(key.getBytes());
21         byte[] bKey = md5.digest();
22         //具体的哈希函数实现细节--每个字节 & 0xFF 再移位
23         long result = ((long) (bKey[3] & 0xFF) << 24)
24                 | ((long) (bKey[2] & 0xFF) << 16
25                         | ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF));
26         return result & 0xffffffffL;
27     }
28 }
复制代码

 

实现一致性哈希算法:

为什么要引入虚拟机器节点?它的作用是什么?

引入虚拟机器节点,其目的就是为了解决数据分配不均衡的问题。因为,在将实际的物理机器映射到环上时,有可能大部分机器都映射到环上的某一个部分(比如左半圆上),而通过引入虚拟机器节点,在进行机器hash映射时,不是映射具体机器,而是映射虚拟机器,并保证虚拟机器对应的物理机器是均衡的---每台实际的机器对应着相等数目的Virtual nodes。如下图:

 

 

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

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

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

 

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

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

 

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

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

 

 完整代码:

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

 

 

参考资料:五分钟理解一致性哈希算法(consistent hashing)

 一致性hash算法 - consistent hashing

一致性哈希算法介绍,及java实现

本文转自hapjin博客园博客,原文链接:http://www.cnblogs.com/hapjin/,如需转载请自行联系原作者


 

相关文章
|
16天前
|
缓存 JavaScript Java
常见java OOM异常分析排查思路分析
Java虚拟机(JVM)遇到内存不足时会抛出OutOfMemoryError(OOM)异常。常见OOM情况包括:1) **Java堆空间不足**:大量对象未被及时回收或内存泄漏;2) **线程栈空间不足**:递归过深或大量线程创建;3) **方法区溢出**:类信息过多,如CGLib代理类生成过多;4) **本机内存不足**:JNI调用消耗大量内存;5) **GC造成的内存不足**:频繁GC但效果不佳。解决方法包括调整JVM参数(如-Xmx、-Xss)、优化代码及使用高效垃圾回收器。
88 15
常见java OOM异常分析排查思路分析
|
21天前
|
缓存 JavaScript Java
常见java OOM异常分析排查思路分析
Java虚拟机(JVM)遇到 OutOfMemoryError(OOM)表示内存资源不足。常见OOM情况包括:1) **Java堆空间不足**:内存被大量对象占用且未及时回收,或内存泄漏;解决方法包括调整JVM堆内存大小、优化代码及修复内存泄漏。2) **线程栈空间不足**:单线程栈帧过大或频繁创建线程;可通过优化代码或调整-Xss参数解决。3) **方法区溢出**:运行时生成大量类导致方法区满载;需调整元空间大小或优化类加载机制。4) **本机内存不足**:JNI调用或内存泄漏引起;需检查并优化本机代码。5) **GC造成的内存不足**:频繁GC但效果不佳;需优化JVM参数、代码及垃圾回收器
常见java OOM异常分析排查思路分析
|
4天前
|
Java
JAVA并发编程系列(9)CyclicBarrier循环屏障原理分析
本文介绍了拼多多面试中的模拟拼团问题,通过使用 `CyclicBarrier` 实现了多人拼团成功后提交订单并支付的功能。与之前的 `CountDownLatch` 方法不同,`CyclicBarrier` 能够确保所有线程到达屏障点后继续执行,并且屏障可重复使用。文章详细解析了 `CyclicBarrier` 的核心原理及使用方法,并通过代码示例展示了其工作流程。最后,文章还提供了 `CyclicBarrier` 的源码分析,帮助读者深入理解其实现机制。
|
10天前
|
设计模式 架构师 Java
Java开发工程师转架构师需要学习什么
Java开发工程师转型为架构师需掌握多项技能:精通Java及框架、数据库与分布式系统;熟悉设计模式与架构模式;积累项目经验;提升沟通与领导力;持续学习新技术;培养系统设计与抽象能力;了解中间件及开发工具;并注重个人特质与职业发展。具体路径应结合个人目标与实际情况制定。
39 18
|
24天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
101 6
【Java学习】多线程&JUC万字超详解
|
2天前
|
XML JavaScript 前端开发
学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
8 2
|
4天前
|
算法 搜索推荐 开发者
别再让复杂度拖你后腿!Python 算法设计与分析实战,教你如何精准评估与优化!
在 Python 编程中,算法的性能至关重要。本文将带您深入了解算法复杂度的概念,包括时间复杂度和空间复杂度。通过具体的例子,如冒泡排序算法 (`O(n^2)` 时间复杂度,`O(1)` 空间复杂度),我们将展示如何评估算法的性能。同时,我们还会介绍如何优化算法,例如使用 Python 的内置函数 `max` 来提高查找最大值的效率,或利用哈希表将查找时间从 `O(n)` 降至 `O(1)`。此外,还将介绍使用 `timeit` 模块等工具来评估算法性能的方法。通过不断实践,您将能更高效地优化 Python 程序。
16 4
|
2天前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
11 1
|
21天前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。
|
8天前
|
机器学习/深度学习 存储 人工智能
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计
使用Python作为开发语言,基于文本数据集(一个积极的xls文本格式和一个消极的xls文本格式文件),使用Word2vec对文本进行处理。通过支持向量机SVM算法训练情绪分类模型。实现对文本消极情感和文本积极情感的识别。并基于Django框架开发网页平台实现对用户的可视化操作和数据存储。
19 0
文本情感识别分析系统Python+SVM分类算法+机器学习人工智能+计算机毕业设计