前言

和每个服务器都部署着相同应用的应用服务器集群不同,分布式缓存服务器集群中不同服务器中缓存的数据各不相同。

缓存访问请求不可以在缓存服务器集群中的任意一台处理,必须先找到缓存有需要数据的服务器,然后才能访问。

这个特点会严重制约分布式缓存集群的伸缩性设计,因为新上线的缓存服务器没有缓存任何数据,而已经下线的缓存服务器还缓存着网站的许多热点数据。

必须让新上线的缓存服务器对整个分布式缓存集群影响最小,也就是说新加入的缓存服务器后应该使整个缓存服务器集群中已经缓存的数据尽可能还被访问到,这是分布式缓存集群伸缩性设计的最主要目标。

哈希分布

由前言的讨论可知,对于分布式缓存服务器集群的管理,路由算法至关重要,和负载均衡算法一样,决定着究竟该访问集群中的哪台服务器。

简单的路由算法可以使用 Hash 算法,即令数据哈希分布:用服务器数目除缓存数据KEY的 Hash 值,余数为服务器列表下标编号。hash(key)%N。

使用 Hash 算法能够使缓存数据在整个分布式缓存集群中比较均衡地分布。对 Hash 路由算法稍加改进,就可以实现和负载均衡算法中加权负载均衡一样的加权路由。事实上,如果不需要考虑缓存服务器集群的伸缩性,余数 Hash 几乎满足绝大多数的缓存路由需求。

但是!!!当分布式缓存集群需要扩容的时候,就麻烦了。。。

简单举个例子,缓存服务器由 3 台扩容至 4 台。更改服务器列表,仍旧使用Hash路由算法,很容易计算出缓存未命中的概率达到了 75%,这个概率会随着服务器节点数量增加变得越来越大

很明显这是我们无法接受的,在网站业务中,大部分的业务数据读操作请求事实上是通过缓存获取的,如果缓存大面积失效,数据访问的压力将重重的落在数据库身上,这将大大超过数据库的负载能力,严重的话可能会导致数据库宕机。

一致性Hash算法

一致性Hash算法(Distributed Hash Table)就是解决上述问题的一个非常好的方案。

首先,构造一个长度为2^32的整数环(长度可以自定义,这个环被称作一致性Hash环)。
分布式缓存的一致性Hash算法

之后,根据节点名称(可以是服务器的IP地址,也可是服务器编号的下标)的Hash值(分布范围在[0 ,(2^32)-1]),将缓存服务器节点放置在这个Hash环上。

如下图所示,我们有 4 个缓存服务器,将这 4 个节点放置在环上。
分布式缓存的一致性Hash算法

然后根据需要缓存的数据的KEY值计算得到其Hash值(其分布范围也在[0 ,(2^32)-1]),然后在Hash环上顺时针查找距离这个KEY的Hash值最近的缓存服务器节点,最近遇到的这个服务器节点就是其应该定位到的服务器节点。

如下图所示,对象B存储在节点B,对象C存储在节点C,对象D存储在节点D,对象A存储在节点A。
分布式缓存的一致性Hash算法

一致性Hash算法在增加或者删除缓存服务器节点时只会影响到哈希环中相邻的节点

例如下图中新增节点 X,只需要将它前一个节点 C 上的数据重新进行分布即可,对于节点 A、B、D 都没有影响。
分布式缓存的一致性Hash算法

但是,这样实现的一致性Hash算法有个小问题,那就是数据很有可能分配不均匀。

有可能某些节点存储了大量数据,而另一些节点存储着少量数据,在各缓存服务器性能相同的情况下,这明显不是我们想要的结果。

为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算多个哈希,每个计算结果位置都放置一个此服务节点,称为虚拟节点。

具体做法可以先确定每个物理节点关联的虚拟节点数量,然后在ip或者主机名后面增加编号。例如上面的情况,可以为每台服务器计算三个虚拟节点,于是可以分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,于是形成六个虚拟节点:

分布式缓存的一致性Hash算法

显然,每个物理节点对应的虚拟节点越多,各个物理节点之间的负载越均衡,新加入的物理服务器对原有的物理服务器的影响越保持一致(这就是一致性Hash这个名称的由来)

参考内容:

《大型网站技术架构》 - 李智慧著

一致性哈希算法

分类:

技术点:

相关文章: