redis集群模式详解

目录

前言:

三种主流分片方式

哈希求余

一致性哈希算法

哈希槽分区算法(redis采用)

问题1:redis集群最多有16384个分片吗?

问题2:为什么是16384个槽位?

故障处理

故障判定

故障迁移

集群宕机

基于Docker搭建集群

小结:


前言:

    redis集群模式初心是为了引入更多硬件资源,提供高可用的集群服务。只要是多个机器构成的分布式系统都可称之为服务器集群,而redis集群可以认为是狭义上的集群,主要是为了解决存储空间不足的问题。

    redis集群模式中,数据是存储在多个主节点上,每个主节点都会搭配多个从节点。每个主节点被称为 分片

三种主流分片方式

    既然对数据做了分片存储,那么在读取数据的时候,就需要确定数据在哪个分片上。

哈希求余

    借鉴了哈希表的基本思想,借助hash函数,把一个key映射到的整数,再对分片数量进行求余,就可以得到具体的分片下标。后续查询key的时候,也是同样的做法。key是一样的,hash函数是一样的,那么得到的分片值就是一样的。

    一旦服务器集群需要扩容,就需要引入分片,那么分片的数量就会改变。原数据所在的分片就不一定是合理的了,那么就需要将数据重新进行哈希求余,分配到新集群中的分片上(类似hash表扩容)

    增加分片之后,原数据大多数都不应该待在当前分片中了,那么这些数据就全部需要重新hash到新的分片上。这就涉及了数据的搬运,需要将原分片所在key搬运到新分片上,并且所有从节点数据也需要搬运,这对于服务器集群资源消耗是相当大的。往往不会直接在生产环境上操作,只能通过替换的方式操作。因为这种算法需要搬运大量的key。

    替换:重新构造一组新服务器集群,将原数据全部复制过来,然后替换原有集群。依赖的机器就会更多,成本更高,操作也更复杂。

一致性哈希算法

    在哈希求余这种操作中,当前key是属于哪个分片,是交替出现的,因此需要搬运大量key。(1 % 3 = 1   2 % 3 = 2   3 % 3 == 0)

    在一致性哈希算法中,把交替出现改进成了连续出现。可以有效解决上述数据搬运的开销。把0 - 2^32 -1 这个数据空间,映射到一个圆环上,数据按照顺时针方向增长。

    假设当前存在三个分片,就把分片放到圆环的某个位置上。假定有⼀个key,计算得到hash值H,从H所在位置,顺时针往下找,找到的第⼀个分片,即为该key所从属的分片。

注意:

    一致性哈希算法每个分片管理一部分数据,只要数据哈希得到的值在这个区间内,都属于当前分片。

    这里进行集群扩容,比如增加三号分片,只需要将0号分片的一部分数据搬运到3号分片即可,一号和2号分片数据是不需要挪动的。

    但是这里每个分片上的数据量就有差异了(数据倾斜),那么实际中每个分片所承载的请求数也有所差异,因此redis并没有采取这样的方案。

哈希槽分区算法(redis采用)

    可以认为这个算法是结合了上述两种思想,可以解决数据搬移量问题和数据倾斜问题。
槽位计算公式:hash_slot = crc16(key) % 16384,crc是一种计算hash值的算法,总共有16384个槽位。

    将这些槽位分配给具体的分片,存储数据时首先会根据key计算槽位,该槽位属于哪一个分片所持有槽位区间,就存储该分片上。读取数据也是一样的。

    在集群扩容的时候,当增加分片后,可以将之前每个分片的部分槽位绑定到新分片上。只有被移动的槽位数据才需要搬运。

    只要每个分片上的槽位数差不多,那么数据就是均匀分配的。当集群扩容后,肯定是需要挪动数据的,这样的算法可以认为挪动的数据量已经是最小的。

    此处每个分片会使用位图这样的数据结构表示自己当前持有哪些槽位号。16384个bit位(2KB),每一位用0/1来区分自己是否持有该槽位。

假设有3个分片,那么有一种分配方式:

    1)0号分片:[0,5461]共5462个槽位

    2)1号分片:[5462,10923]共5462个槽位

    3)2号分片:[10924,16383]共5460个槽位

注意:

    不一定是严格意义上的均匀分配,此时就可以认为三个分片上的数据比较均匀了。

    每个分片所持有的槽位号,可以是连续的,也可以是不连续的。redis分片所持有的槽位号都是可以手动配置的。

问题1:redis集群最多有16384个分片吗?

    理论上是这样,但实际redis作者建议集群分片数不应该超过1000

    如果存在16384分片,那么每个分片只有一个槽位。由于key需要先映射到槽位再映射到分片,那么这样有的分片可能有多个key,有的分片可能没有key。此时就很难保证数据在各个分片上的均衡性。

    如果每个分片上的槽位比较多,如果槽位个数相当,就可以认为包含key的数量是相当的。

    如果每个分片上的槽位比较少,槽位个数不一定能直观反映到key的数目。

问题2:为什么是16384个槽位?

    对于分片数量这个数值基本是够用的,同时占用的网络带宽也不大。

    redis集群需要周期发送心跳包判定节点是否运作正常,心跳包中包含了该节点持有哪些slots。

    16384个字节是2KB,那么每个心跳包都需要包含2KB的数据。虽然2KB不是很大,但对于网络传输,周期性发送心跳包机制是权衡过比较合适的。

故障处理

故障判定

识别某个节点是否挂了,心跳包机制。

    1)节点A给节点B发送ping包,B就会给A返回⼀个pong包。ping和pong除了 message type属性之外,其他部分都是⼀样的。这里包含了集群的配置信息(该节点的id,该节点从属于哪个分片是主节点还是从节点,从属于谁,持有哪些slots的位图...).。

    2)每个节点,每秒钟,都会给⼀些随机的节点发起ping包,而不是全发⼀遍。这样设定是为了避免在节点很多的时候,心跳包也非常多(比如有9个节点,如果全发,就是9*8有72组心跳了,而且这是按照N^2这样的级别增长的).

    3) 当节点A给节点B发起ping包,B不能如期回应的时候,此时A就会尝试重置和B的tcp连,看能否连接成功。如果仍然连接失败,A就会把B设为PFAIL状态(相当于主观下线).

    4)A判定B为PFAIL之后,会通过redis内置的Gossip协议,和其他节点进行沟通,向其他节点确认B的状态。(每个节点都会维护一个自己的"下线列表",由于视角不同,每个节点的下线列表也不⼀定相同).

    5)此时A发现其他很多节点,也认为B为PFAIL,并且数目超过总集群个数的一半,那么A就会把B标记成FAIL(相当于客观下线),并且把这个消息同步给其他节点(其他节点收到之后,也会把B标记成FAIL)。至此,B就彻底被判定为故障节点了。

故障迁移

    如果B是从节点,那么不需要进行故障迁移。

    如果B是主节点,那么就会由B的从节点(比如C和D)触发故障迁移了。所谓故障迁移,就是指把从节点提拔成主节点,继续给整个redis集群提供支持。

    1)从节点判定自己是否具有参选资格。如果从节点和主节点已经太久没通信(此时认为从节点的数据和主节点差异太大了)时间超过阈值,就失去竞选资格。

    2) 具有资格的节点,比如C和D,就会先休眠⼀定时间。休眠时间 = 500ms基础时间 + [0,500ms 随机时间  +  排名 * 1000ms,offset的值越大,则排名越靠前(越小,休眠时间越短)。

    3) 比如C的休眠时间到,C就会给其他所有集群中的节点,进行拉票操作。但是只有主节点才有投票资格。

    4)主节点就会把自己的票投给C(每个主节点只有1票)。当C收到的票数超过主节点数目的一半,C 就会晋升成主节点。(C自己负责执行slave of noone,并且让D执行slaveof C)。

    5)同时,C还会把自己成为主节点的消息,同步给其他集群的节点。大家也都会更新自己保存的集群结构信息。

注意:

    上述选举过程称为 Raft 算法,是一种在分布式系统中广泛使用的算法。在随机休眠时间加持下,基本上就是谁先唤醒,谁就能竞选成功。

集群宕机

1)某个分片,所有主节点和从节点都挂了。

2)某个分片,主节点挂了,但没有从节点。

3)超过半数的master都挂了(如果突然遇到一系列master都挂了,那么说明集群出现了严重问题)

基于Docker搭建集群

小结:

    redis集群目的就是为了提供更多数据存储,那么就需要引入更多硬件资源,因此就需要一些机制来保证集群高可用的性能。

你可能感兴趣的:(redis,redis,数据库,缓存)