背景
在分布式系统中,对数据的准确定位以及整个系统的结构具有很高的要求。主要有三种算法:
- hash 算法(大量缓存重建)
- 一致性 hash 算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)
- redis cluster 的 hash slot 算法(也叫hash槽)
适用性
- hash算法比较适合固定分区或者分布式节点的集群架构。
- 一致性hash算法比较适合需要动态扩容的分布式架构以及一些动态负载均衡的分布式中间件和RPC中间件。
- hash slot是Redis对hash算法的一种实现。
算法
hash算法
shard=hash(ip)%(NUMS_HANDLER)
- 类似哈希桶的原理,将不同的请求哈希碰撞到固定的「哈希桶」中,进而找到对应的请求,但是当系统需要对服务器进行扩容时,那么整个已经保存到服务器中的数据都得重新进行哈希碰撞,系统具有海量数据时将会是场灾难
一致性hash算法
node = hash(key) % n // 其中n取2^32
-
一致性哈希将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为 0 0 0到 2 32 − 1 2^{32}-1 232−1(即哈希值是一个32位无符号整形)
流程
-
在集群服务器确定以后,将各个服务器使用hash函数进行一个哈希计算,哈希计算可以选择服务器的ip地址或者主机名等关键字进行哈希,这样就完成了节点在hash环上的位置分配,假设集群有四台服务器
-
每台服务器在hash环上的位置分配完成后,在客户端对缓存key进行同样函数的hash运算,得出hash值,同样得到环上的一个位置,从这个位置顺时针找到最近的一个服务器节点,比如遍历所有节点位置和key位置差值取最小值,这样就完成了路由
-
当需要对系统扩容时,我们依然需要进行数据的迁移,但是只是部分的,只需要迁移1-2节点之间的数据即可。相对hash取模,一致性hash算法减少了扩容带来的数据迁移量太大的问题
-
一致性hash算法有良好的容错性和拓展性
缺陷
-
无法控制节点分布的均匀性,因为hash的结果并不一定均匀分布在环上对称的位置
-
如
pic b
的效果,当很多节点落到1和2节点之间,少量节点落到3和4之间,便会带来1和2节点的请求倾斜的问题
解决
- 添加虚拟节点(如上图中的1的另外两个虚拟节点1’和1’’)。具体操作就是将一台服务器加上编号尾缀进行哈希,每台服务器就会有多个结果,简单来说就是将一台服务器映射成不同的多个hash值
-
分布式缓存系统中一致性hash算法满足的条件:
- 平衡性(Balance):哈希的结果能够尽可能分布到所有的缓冲区中去,这样可以使得所有的缓冲空间都得到利用
- 单调性(Monotonicity):如果已经有一些内容通过哈希分派到了相应的缓冲区中,又有新的缓冲区加入到系统中,那么哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲区中去,而不会被映射到旧的缓冲集合中的其他缓冲区
- 分散性(Spread):各个终端导致的hash不一致的问题
- 负载性(Load):同的终端可能将相同的内容映射到不同的缓冲区中
- 平滑性(Smoothness):缓存服务器的数目平滑改变和缓存对象的平滑改变是一致的
hash槽
- hash slot即hash槽。redis cluster采用的正式这种hash槽算法实现的寻址。以redis cluster为例。在redis cluster中固定的存在16384个hash slot。
hash slot = CRC16(key)%16384;
- 实现原理:
- 记录和物理机之间引入了虚拟桶层,记录通过hash函数映射到虚拟桶,记录和虚拟桶是多对一的关系;
- 第二层是虚拟桶和物理机之间的映射,同样也是多对一的关系,即一个物理机对应多个虚拟桶,这个层关系是通过内存表实现的。
现在有3个节点已经组成了集群,分别是:1、2、3三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用hash槽 (hash slot)的方式来分配16384个slot 的话,它们三个节点分别承担的slot 区间是:
节点1覆盖0-5460
节点2覆盖5461-10922
节点3覆盖10923-16383
如果用户将新节点 4 添加到集群中, 那么集群只需要将节点 1、2、3中的某些槽移动到节点 4 就可以了。比如可以像如下操作:
节点1覆盖1365-5460
节点2覆盖6827-10922
节点3覆盖12288-16383
节点4覆盖0-1364,5461-6826,10923-12287
如果用户要从集群中移除节点1 , 那么集群只需要将节点1中的所有hash槽移动到节点 2和节点 3 , 然后再移除空白(不包含任何哈希槽)的节点 1就可以了。
因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。