一致性 Hash 原理与实现

Redis 集群的使用

在使用 Redis 的过程中,为了保证 Redis 的高可用,我们一般会对 Redis 做主从复制,组成Master-Master 或者 Master-Slave的形式,进行数据的读写分离。

一致性 Hash 原理与实现
举个例子,我们有一个电商平台,需要使用 Redis 存储商品的图片资源,存储的格式为键值对, key 值为图片名称,value 为该图片所在的文件服务器的路径,我们需要根据文件名,查找文件所在的文件服务器上的路径,我们的图片数量大概在 3000w 左右,按照我们的规则进行分库,规则就是随机分配的,我们以每台服务器存 500w 的数量,部署 12 台缓存服务器,并且进行主从复制,架构图如下图所示:

一致性 Hash 原理与实现
由于我们定义的规则是随机的,所以我们的数据有可能存储在任何一组 Redis 中,比如我们需要查询“produc.png”的图片,由于规则的随机性,我们需要遍历所有 Redis 服务器,才能查询得到。这样的结果显然不是我们所需要的。所以我们会想到按某一个字段值进行 Hash 值、取模。所以我们就看看使用 Hash 的方式是怎么进行的。

使用 Hash 的 Redis 集群

如果我们使用 Hash 的方式,每一张图片在进行分库的时候都可以定位到特定的服务器,示意图如图所示:

一致性 Hash 原理与实现
从上图中,我们需要查询的是图product.png,由于我们有 6 台主服务器,所以计算的公式为:hash(produc.png) % 6 = 5,我们就可以定位到是 5 号主从,这样就省去了遍历所有服务器的时间,从而大大提升了性能。

使用 Hash 时遇到的问题

在上述 hash 取模的过程中,我们虽然不需要对所有 Redis 服务器进行遍历而提升了性能。但是,使用 Hash 算法缓存时会出现一些问题,Redis 服务器变动时,所有缓存的位置都会发生改变

比如,现在我们的 Redis 缓存服务器增加到了 8 台,我们计算的公式从 hash(product.png) % 6 = 5 变成了 hash(product.png) % 8 = ? 结果肯定不是原来的 5 了。

再者,6 台服务集群中,当某个主从集群出现故障时,无法进行缓存,那我们需要把故障机器移除,所以取模又会从 6 变成了 5。我们计算的公式也会变化。

由于上面 hash 算法是使用取模来进行缓存的,为了规避上述情况,Hash 一致性算法就诞生了。

一致性 Hash 算法原理

一致性 Hash 算法也是使用取模的方法,不过,上述的取模方法是对服务器的数量进行取模,而一致性的 Hash 算法是对 2^32取模。即,一致性 Hash 算法将整个 Hash 空间组织成一个虚拟的 圆环,Hash 函数的值空间为0 ~ 2^32 -1(一个32位无符号整型),整个哈希环如下:

一致性 Hash 原理与实现

现在,我们使用以下算法定位数据访问到相应的服务器:

将数据 key 使用相同的函数 Hash 计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针查找,遇到的服务器就是其应该定位到的服务器。

例如,现在有 ObjectA、ObjectB、ObjectC 是哪个数据对象,经过哈希计算后,在环空间上的位置如下:

一致性 Hash 原理与实现
根据一致性算法:

  • ObjectA -> NodeA
  • ObjectB -> NodeB
  • ObjectC -> NodeC

一致性 Hash 算法的容错性和可扩展性

现在,假设我们的 NodeC 宕机了,我们从图中可以看到,A、B 不会受到影响,只有 ObjectC 对象被重新定位到 NodeA。所以我们发现,在一致性 Hash 算法中,如果一台服务器不可用,受影响的数据仅仅是此服务器到其环空间前一台服务器之间的数据(这里为 NodeC 到 NodeB 之间的数据),其他不会受到影响。如图所示:

一致性 Hash 原理与实现
另外一种情况,现在我们系统增加了一台服务器 NodeX,如图所示:

一致性 Hash 原理与实现
此时对象 ObjectA、ObjectB 没有受到影响,只有 ObjectC 重新定位到了新的节点 X 上。
如上所述:

一致性 Hash 算法对于节点的增减都只需要重定位环空间中的一小部分数据,有很好的容错性和可扩展性。

数据倾斜问题

在一致性 Hash 算法服务节点太少的情况下,容易因为几点分布不均匀造成数据倾斜(被缓存的对象大部分缓存在某一台服务器上)的问题,如图特例:

一致性 Hash 原理与实现

这时我们发现有大量数据集中在节点 A 上,而节点 B 只有少量数据。为了解决数据倾斜问题,一致性 Hash 算法引入了虚拟节点机制,即对每一个服务器节点计算多个哈希,每个计算结果未知都放置一个此服务节点,称为虚拟节点。

具体操作可以为服务器 IP 或主机名后加入编号来实现,实现如图所示:
一致性 Hash 原理与实现
数据定位算法不变,只需要增加一步:虚拟节点到实际点的映射。
所以加入虚拟节点之后,即使在服务节点很少的情况下,也能做到数据的均匀分布。

转载于:一致性Hash原理与实现(Java)