图解一致性hash算法和实现

更多内容,欢迎关注微信公众号:全菜工程师小辉。公众号回复关键词,领取免费学习资料。node

一致性hash算法是什么?

一致性hash算法,是麻省理工学院1997年提出的一种算法,目前主要应用于分布式缓存当中。
一致性hash算法能够有效地解决分布式存储结构下动态增长和删除节点所带来的问题。
在Memcached、Key-Value Store、Bittorrent DHT、LVS中都采用了一致性hash算法,能够说一致性hash算法是分布式系统负载均衡的首选算法。算法

传统hash算法的弊端

经常使用的算法是对hash结果取余数 (hash() mod N):对机器编号从0到N-1,按照自定义的hash算法,对每一个请求的hash值按N取模,获得余数i,而后将请求分发到编号为i的机器。但这样的算法方法存在致命问题,若是某一台机器宕机,那么应该落在该机器的请求就没法获得正确的处理,这时须要将宕掉的服务器使用算法去除,此时候会有(N-1)/N的服务器的缓存数据须要从新进行计算;若是新增一台机器,会有N /(N+1)的服务器的缓存数据须要进行从新计算。对于系统而言,这一般是不可接受的颠簸(由于这意味着大量缓存的失效或者数据须要转移)。
缓存

传统求余作负载均衡算法,缓存节点数由3个变成4个,缓存不命中率为75%。计算方法:穷举hash值为1-12的12个数字分别对3和4取模,而后比较发现只有前3个缓存节点对应结果和以前相同,因此有75%的节点缓存会失效,可能会引发缓存雪崩。服务器

一致性hash算法

  1. 首先,咱们将hash算法的值域映射成一个具备232 次方个桶的空间中,即0~(232)-1的数字空间。如今咱们能够将这些数字头尾相连,组合成一个闭合的环形。
  2. 每个缓存key均可以经过Hash算法转化为一个32位的二进制数,也就对应着环形空间的某一个缓存区。咱们把全部的缓存key映射到环形空间的不一样位置。
  3. 咱们的每个缓存节点也遵循一样的Hash算法,好比利用IP或者主机名作Hash,映射到环形空间当中,以下图

image

  1. 如何让key和缓存节点对应起来呢?很简单,每个key的顺时针方向最近节点,就是key所归属的缓存节点。因此图中key1存储于node1,key2,key3存储于node2,key4存储于node3。

image

  1. 当缓存的节点有增长或删除的时候,一致性哈希的优点就显现出来了。让咱们来看看实现的细节:
  • 增长节点
    当缓存集群的节点有所增长的时候,整个环形空间的映射仍然会保持一致性哈希的顺时针规则,因此有一小部分key的归属会受到影响。

image

有哪些key会受到影响呢?图中加入了新节点node4,处于node1和node2之间,按照顺时针规则,从node1到node4之间的缓存再也不归属于node2,而是归属于新节点node4。所以受影响的key只有key2。微信

image

最终把key2的缓存数据从node2迁移到node4,就造成了新的符合一致性哈希规则的缓存结构。数据结构

  • 删除节点
    当缓存集群的节点须要删除的时候(好比节点挂掉),整个环形空间的映射一样会保持一致性哈希的顺时针规则,一样有一小部分key的归属会受到影响。

image

有哪些key会受到影响呢?图中删除了原节点node3,按照顺时针规则,本来node3所拥有的缓存数据就须要“托付”给node3的顺时针后继节点node1。所以受影响的key只有key4。负载均衡

image

最终把key4的缓存数据从node3迁移到node1,就造成了新的符合一致性哈希规则的缓存结构。分布式

说明:这里所说的迁移并非直接的数据迁移,而是在查找时去找顺时针的后继节点,因缓存未命中而刷新缓存。函数

计算方法:假设节点hash散列均匀(因为hash是散列表,因此并非很理想),采用一致性hash算法,缓存节点从3个增长到4个时,会有0-33%的缓存失效,此外新增节点不会环节全部原有节点的压力。学习

一致性hash算法的结果相比传统hash求余算法已经进步不少,但可不能够改进一下呢?或者若是出现分布不均匀的状况怎么办?好比下图这样,按顺时针规则,全部的key都归属于统一个节点。

image

一致性hash算法+虚拟节点

为了优化这种节点太少而产生的不均衡状况。一致性哈希算法引入了虚拟节点的概念。
所谓虚拟节点,就是基于原来的物理节点映射出N个子节点,最后把全部的子节点映射到环形空间上。

image

虚拟节点越多,分布越均匀。使用一致性hash算法+虚拟节点这种状况下,缓存节点从3个变成4个,缓存失效率为25%,并且每一个节点都平均的承担了压力。

一致性hash算法+虚拟节点的实现

原理理解了,实现并不难,主要是一些细节:

  1. hash算法的选择。Java代码不要使用hashcode函数,这个函数结果不够散列,并且会有负值须要处理。
    这种计算Hash值的算法有不少,好比CRC32HASH、FNV132HASH、KETAMAHASH等,其中KETAMAHASH是默认的MemCache推荐的一致性Hash算法,用别的Hash算法也能够,好比FNV132_HASH算法的计算效率就会高一些。
  2. 数据结构的选择。根据算法原理,咱们的算法有几个要求:
  • 要能根据hash值排序存储
  • 排序存储要被快速查找 (List不行)
  • 排序查找还要能方便变动 (Array不行)

另外,因为二叉树可能极度不平衡。因此采用红黑树是最稳妥的实现方法。Java中直接使用TreeMap便可。

更多内容,欢迎关注微信公众号:全菜工程师小辉。公众号回复关键词,领取免费学习资料。

哎呀,若是个人名片丢了。微信搜索“全菜工程师小辉”,依然能够找到我

相关文章
相关标签/搜索