从工程实践的角度理解一致性哈希算法(Consistent Hashing)


注:一致性哈希算法可以解决后台数据库服务器以及缓存服务器在数据存储 访问和持久化等方面的诸多问题,而对于分布式数据存储更是有锦上添花的功效。为了后续关于PostgreSQL数据库的相关功能开发(例如Oracle Rac等),有必要再捋一捋一致性哈希算法的基本原理和特性说明。

饭前小甜点:现在我们假设有5台Redis 数据库服务器,一份数据对象Object 6进来的时候,以散列公式hash(i)%5,计算所存放的服务器,假设Hash(i) = i%N,那么数据被散列到标号为1的服务器。可是如果这个时候服务器新增了一台,然后散列公式为Hash(i)%6,这个时候请求访问数据Object 6的时候,被分配至0号服务器,但是其实这个时候数据是在1号服务器的。

于是 这就是一个问题了 !!??!!

需求背景

在实际的应用中:我们在使用N台缓存服务器(Cache)时,一种常用的负载均衡方式就是:对某一资源object x的请求使用简单线性哈希:hash(x) = (ax + b) mod (N)来计算Object的hash值,并均匀的映射到某一台缓存服务器上。

一、但是当增加或减少一台缓存服务器时,这种方式可能就会改变所有资源对应的hash值,也即大量的缓存都失效了,这会使得缓存服务器大量集中地向原始内容服务器更新缓存.与此同时 这实质上意味着顷刻之间所有的Cache都失效了,而这对于我们后台服务器而言简直是一场灾难:潮水般的访问请求直接冲击后台服务器)。
二、简单的线性映射哈希无法实现与高新技术兴起之后越来越强的硬件能力的完美搭配,也许之后的硬件节点在存储和计算上面性能更加优异 而取余映射的算法是无法区分新旧设备的

因此可以避免如上这般问题的一致哈希算法(Consistent Hashing)就应用而生。

基本原理

一言以蔽之:一致性哈希算法尽可能使同一个资源映射到同一台缓存服务器(在移除/添加一台Cache的时候,尽可能小的改变已有的K-V映射关系 并尽可能的满足单调性)。

所谓哈希算法的单调性:指的是如果之前已经有一些对象通过哈希算法被分派到相应的缓存之中,即使有新的Cache加入 哈希的结果也应保证原有已分派的对象可以被映射至新的Cache里面,而非被映射到旧的缓存集合中的其他缓存上。于是简单线性哈希:hash(x) = (ax + b) mod (N) 就不太满足单调性需求了!

说的再详细一点就是:一致性哈希算法要求增加一台缓存服务器时,新的服务器尽量分担存储其他所有服务器的缓存资源;减少一台缓存服务器时,其他所有服务器也可以尽量分担存储它的缓存资源。

一致哈希算法的主要思想是:将每个缓存服务器与一个或多个哈希值域区间关联起来,其中区间边界通过计算缓存服务器对应的哈希值来决定的。(定义区间的哈希函数不一定和计算缓存服务器哈希值的函数相同,但是两个函数的返回值的范围需要匹配。)如果一个缓存服务器被移除,则它所对应的区间会被并入到邻近的区间,其他的缓存服务器不需要任何改变 。

OK,上面这段话也许一上来就比较突兀,我们下面来详细图解一下这段话真正的含义:(但是希望大家在了解一致性哈希之美之后,用心多体会体会这段话)

Hash环形空间

简单来说,一致性哈希算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数Hash()的值空间为0-2^32-1(即哈希值是一个32位无符号整型值 4G),整个哈希空间环如下:
在这里插入图片描述
注:整个环形空间按顺时针方向组织。0和2^32-1 (将value映射到32位的数值空间)在12点钟中方向重合。

Hash键值映射

接下来的一步就是:将各个Cache 服务器节点 使用Hash()进行一个哈希计算,具体可以选择服务器的IP地址或主机名作为关键字key进行哈希,这样每台机器就能唯一地确定其在哈希圆环上的位置,这里假设将上述的四台Cache服务器使用IP地址哈希计算后在圆环空间的映射位置如下:

Hash(IP 1)=cache1
Hash(IP 2)=cache2
Hash(IP 3)=cache3
Hash(IP 4)=cache4

在这里插入图片描述
服务器节点映射完成之后,下面我们使用如下算法定位数据访问到相应服务器:将数据对象使用相同(不是必须的)的函数Hash()计算出哈希值,并确定此数据在圆环上的位置,从此位置沿环顺时针 “搜索前进”,所遇到的第一个Cache服务器就是其应该定位到的服务器。

例如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希Hash()计算后,在圆环空间上的位置如下:

Hash(Object A)=key1
Hash(Object B)=key2
Hash(Object C)=key3
Hash(Object D)=key4

数据映射图示如下:
在这里插入图片描述
如上图所示:根据一致性哈希算法(顺时针搜索),对象A会被定为到Cache 2上,B被定为到Cache 3上,C被定为到Cache 4上,D被定为到Cache 1上。而且这个过程是可信的:数据对象和缓存服务器节点的Hash()值都是固定且唯一的,那么这个定位的过程也就是可靠的。

节点 增加删除

下面分析一致性哈希算法的容错性和可扩展性

现假设Cache 3不幸宕机(实际中不可避免),可以看到此时对象C、D、A不会受到影响,只有对象B被重定位到Cache 4上面。从事实来看,在一致性哈希算法中,如果一台服务器不可用(Cache 3),则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)(Cache 2)之间数据,其它不会受到影响。
在这里插入图片描述
OK,下面来看一下另外一种情况,如果在系统中增加一台服务器Cache 5,如下图所示:
在这里插入图片描述
如上图所示:此时对象Object C、D、A不受影响,只有对象B需要重定位到新的Cache 5上面 。在这种情况下,在一致性哈希算法中:如果增加一台服务器,则受影响的数据仅仅是新服务器(Cache 5)到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)(Cache 2)之间数据,其它数据也不会受到影响。

综上所述,一致性哈希算法对于缓存服务器节点的增减都只需重定位环空间中的一小部分数据,具有良好的容错性和优异的可扩展性。

映射 数据倾斜

认真观察上面两种情形,当一致性哈希算法的服务器Cache节点太少的时候,容易发生因为节点分部不均匀而造成的数据倾斜问题。例如系统中只有两台服务器,其环分布如下图所示:
在这里插入图片描述
此时必然造成大量数据集中到Cache 2上,而只有极少量的数据会定位到Cache 1上面。当然我们希望的结果(Hash算法平衡性的体现,但Hash算法不能保证绝对的平衡)就是:Hash()之后的结果尽可能的利用上所有的缓存服务器资源,提高利用效率。

Hash 虚拟节点

为了解决这种数据倾斜问题,一致性哈希算法引入了虚拟节点机制,即对每一个服务节点计算出多个哈希映射值,而其中每一个计算结果位置都放置一个此Cache服务器节点,称之为虚拟节点。(一个实际节点对应上 复制个数 个虚拟节点)

具体做法就是:可以选择在服务器IP地址或主机名的后面增加编号区分来实现。例如上面的情况(两台Cache服务器节点),可以为每台服务器计算三个虚拟节点(这个3表示的是:复制个数),分别以计算 “Cache 1#1”、 “Cache 1#2”、 “Cache 1#3”、 “Cache 2#1”、 “Cache 2#1”、 “Cache 2#3”的Hash()哈希值,于是形成六个虚拟节点:
在这里插入图片描述
如上图所示:虚拟节点在逻辑上增加的同时,数据定位算法Hash()不变,只是多了一步虚拟节点到实际节点的映射,例如定位到“Cache 1#1”、 “Cache 1#2”、 “Cache 1#3”三个虚拟节点的数据均定位到Cache 1上面。此时的映射关系就是:

1、Object B和Object D映射到Cache 1的虚拟节点上
2、Cache 1的几个虚拟节点到实际的Cache 1的映射
3、新的映射关系:{对象–>虚拟节点}

在这里插入图片描述
这样就解决了服务节点少时数据倾斜的问题。在实际应用中,通常将虚拟节点数设置为32甚至更大,因此即使很少的服务节点也能做到相对均匀的数据分布。

猜你喜欢

转载自blog.csdn.net/weixin_43949535/article/details/106972763
今日推荐