深入了解一下一致性Hash算法
Posted lebronchen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入了解一下一致性Hash算法相关的知识,希望对你有一定的参考价值。
上一篇文章介绍了一下一致性Hash算法,也分析了一致性Hash算法的好处和使用场景。今天再来深入了解一下一致性Hash算法。
Hash算法
上一篇文章说过要对节点和需要存储的key使用Hash算法使它们落在0-2^32-1的Hash环上,那么这个Hash算法是怎么实现的呢?
假设我们缓存服务器的三台节点分别是192.168.0.100、192.168.0.101和192.168.0.102,我们来对比一下集中Hash算法
String类的hashCode方法
我们先看一下String的hashCode方法算出来的节点位置
@Test
public void testStringHashCode(){
System.out.println("节点落在Hash环" + "192.168.0.100".hashCode() + "位置");
System.out.println("节点落在Hash环" + "192.168.0.101".hashCode() + "位置");
System.out.println("节点落在Hash环" + "192.168.0.102".hashCode() + "位置");
}
控制台输出
节点落在Hash环-2052196780位置
节点落在Hash环-2052196779位置
节点落在Hash环-2052196778位置
使用String类的hashCode方法可以看到这三个节点的hash值都是负数,并且紧挨着。
负数好解决,可以对结果进行取绝对值,但是三个节点紧挨着会导致节点分布不均匀,从而导致大量的缓存数据落在一台机器上。
所以可以很清楚的看到String的hashCode方法并不适合一致性Hash算法。
FNV1_32_HASH算法
目前比较常用的一致性Hash算法有CRC32_HASH、FNV1_32_HASH、KETAMA_HASH等,其中FNV1_32_HASH号称是速度最快的一致性Hash算法,我们再来看一下使用FNV1_32_HASH算法的节点分布:
public static void main(String[] args) {
System.out.println("节点落在Hash环" + FNV1_32_HASH("192.168.0.100") + "位置");
System.out.println("节点落在Hash环" + FNV1_32_HASH("192.168.0.101") + "位置");
System.out.println("节点落在Hash环" + FNV1_32_HASH("192.168.0.102") + "位置");
}
控制台输出
节点落在Hash环802944099位置
节点落在Hash环761674073位置
节点落在Hash环1849601453位置
可以看到虽然1,2两个节点距离也还是比较近,但是和节点3的距离还是比较远的,相对于String的hashCode方法,可以是节点分布的更加均衡。
算法对比
我们对比一下上面说的两个算法
String类的hashCode算法源码:
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
FNV1_32_HASH算法源码:
private int FNV1_32_HASH(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;
}
对比两个算法我们可以看到:
String类的hashCode方法使用了简单的数字相乘相加的运算,所以会导致相似的字符串得出来的Hash值比较接近
FNV1_32_HASH算法使用了大量的异或运算符,使得即使很相似的字符串,算出来的Hash值也会差距很大
通过FNV1_32_HASH算法的实现我们可以看到是最后对hash值取了绝对值,而int类型的最大值是2^31-1,所以我们可以知道FNV1_32_HASH算法实现的Hash环长度其实只有2^31,而非之前介绍的2^32
所以要使用一致性Hash算法来实现分布式缓存,一定要选好Hash算法,FNV1_32_HASH算法速度会快一些,KETAMA_HASH算法时memcache默认使用的算法。
Hash环的数据结构
假设我们采用一种数据结构将所有的节点按顺序存起来了,那么当我们查找一个key的位置的时候,根据顺时针的原则,只要根据该key的hash值去数据结构中从小到大去匹配,第一个大于这个hash值的节点就是存放该key的节点。
首先我们明确的是:在分布式缓存的架构中,我们更在意的是存取的速度,所以一定是时间优于空间的数据结构更适合我们。
这里我们对比一下数组和红黑树的效率
数组
我们可以采用ArrayList来按照节点hash值从小到大存放所有的节点信息,将key的hash值与ArrayList中的元素进行比较,找到第一个大于key的hash值的节点。时间复杂度是O(N)
红黑树
我们可以采用TreeMap来存放所有的节点信息,将key的hash值与TreeMap中的元素进行比较,找到第一个大于key的hash值的节点。时间复杂度是O(logN)
所以从时间复杂度上可以看出红黑树的查找效率是明显优于数组的。
但是红黑树也有自己的缺点:
初始化构建红黑树的速度会比数组慢上5-10倍
红黑树增删节点时为了维护红黑树的平衡,也会比数组慢上很多
但是毕竟不论是初始化构建红黑树还是红黑树增删节点,相对与红黑树的查询来说,次数都会少很多。
所以相较于红黑树的查询查询效率,插入速度慢的缺点就显得不那么重要了。
总结
今天的分享基本上讲了两部分:Hash算法和数据结构的查询速度,感到有帮助的小伙伴点个赞呗。
扫码关注,不迷路
以上是关于深入了解一下一致性Hash算法的主要内容,如果未能解决你的问题,请参考以下文章