深入了解一下一致性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算法的主要内容,如果未能解决你的问题,请参考以下文章

对一致性Hash算法,Java代码实现的深入研究

对一致性Hash算法,Java代码实现的深入研究

对一致性Hash算法,Java代码实现的深入研究

对一致性Hash算法,Java代码实现的深入研究

转载对一致性Hash算法,Java代码实现的深入研究

Java深入研究一致性Hash算法