目录
一, 集群及分片算法
1.1 什么是集群
1.2 数据分片算法
1. 哈希求余
2. 一致性哈希算
3. 哈希槽分区算法(Redis使用)
二, 集群的故障处理
2.1 故障判定
2.2 故障迁移
三, 集群扩容
四, 集群缩容
我们在Redis哨兵中学习了,哨兵+主从复制是为了提高系统的可用性,当主节点发生宕机了,可以自动进行恢复,但是并不能解决"数据极端情况下丢失"的问题,不能提高数据的存储容量,当我们存储的数据接近或者超过机器的无礼内存,这样的结构就难以胜任了,所以引入了集群的概念.
Redis集群就是在上述的思路之下,引入多组Master/Slave,每一组Master/Slave存储数据全集的一部分,从而构成一个更大的整体,称为Redis集群.
假如整个数据全集是1TB,引入三组Master/Slave来存储,那么每一组机器只需要存储整个数据全集的1/3即可.
在上述图中:
这三组机器存储的数据是不同的;每个Slave都是对应Master的备份(当Master挂了,对应的Slave会补位成Master),每个红框部分都可以称为是一个分片(Sharding),如果数量进一步增加,只要增加更多的分片,即可解决.
Redis集群的核心思路是用多组机器来存数据的每个部分,那么接下来的核心问题就是,给定一个数据(一个具体的key),那么这个数据应该存储在哪个分片上?读取的时候又应该去哪个分片读取?围绕这个问题,业界有三种比较主流的实现方式.
设有N个分片,使用[0,N-1]这样序号进行编号
针对某个给定的key,先计算hash值,再把得到的结果%N,得到的结果即为分片编号.
例如,N为3.给定key为hello,对hello计算hash值(比如使用md5算法),得到的结果为"bc4b2a76b9719d91",再把这个结果%3,结果为0,那么就把hello这个key放到0号分片上;实际工作涉及到的系统,计算hash的方式不一定是md5,但是思想是一致的.
后续如果要取某个key的记录,也是针对key计算hash,再对N求余,就可以找到对应的分片编号了.
优点:简单高效,数据分配均匀
缺点:一旦需要进行扩容,N就改变了,原有的映射规则被破坏,就需要让节点之间的数据相互传输,重新排列以满足昕的映射规则,此时需要搬运的数据量是比较多的,开销较大
N为3的时候,[100,120]这21个hash值的分布(此处假定计算出的hash值是一个简单的整数,方便肉眼观察),当引入一个新的分片,N从3->4时,大量的key都需要重新映射(某个key%3和%4的结果不一样,就映射到不同机器上了)
如上图可以看出,整个扩容一共21个key,只有3个key没有经过搬运,其他的key都是搬过的.
为了降低上述的搬运开销,能够更高效扩容,业界提出了"一致性哈希算法",key映射到分片序号的过程不再是简单求余了,而是改成以下过程:
第一步,把 0->2^32-1 这个数据空间,映射到一个圆环上,数据按照顺时针方向增长
第二步,假设当前存在三个分片,就把分片放到圆环的某个位置上
第三步,假定有一个key,计算得到hash值H,那么这个key映射到哪个分片上呢?规则很简单,就是从H所在位置,顺时针往下找,找到的第一个分片,即为该key所从属的分片
这就相当于,N个分片的位置,把整个圆环分成了N个管辖区间,key的hash值落在某个区间内,就归对应区间管理.
在这个情况下,如果扩容一个分片,如何处理呢?
原有分片在环上位置不动,只要在环上新安排一个分片位置即可.
此时只需要把0号分片上的部分数据,搬运给3号分片即可,1号分片和2号分片管理的区间都是不变的
优点:大大降低了扩容时对数据搬运的规模,提高了扩容操作的效率
缺点:数据分配不均匀(有的多有的少,数据倾斜)
为了解决上述问题(搬运成本高和数据分配不均匀),Redis集群引入了哈希槽(hash slots)算法
hash_slot = crc16(key) % 16384
其中crc16也是一种hash算法,通过key计算出对应的key
16384 = 16 * 1024 也就是2^14
相当于把整个哈希值,映射到16384个槽位上,也就是[0,16383],然后再把这些槽位比较均匀的分配给每一个分片,每个分片的节点都需要记录自己持有哪些分片;
假设当前有三个分片,一种可能的分配方式:
这里的分片规则是很灵活的,每个分片持有的槽位也不一定连续,每个分片的节点使用位图来表示自己持有哪些槽位,对于16384个槽位来说,需要2048个字节(2KB)大小的内存空间表示.
如果需要进行扩容,比如新增一个3号分片,就可以针对原有的槽位进行重新分配,比如可以把之前每个分片持有的槽位,各拿出一点,分给新分片,一种可能的分配方式:
我们实际使用Redis集群分片的时候,不需要手动指定哪些槽位分配给某个分片,只需要告诉某个分片应该持有多少个槽位即可,Redis会自动完成后续的槽位分配,以及对应的key搬运的工作.
哈希槽分区算法的相关面试问题:
问题一:Redis集群是最多有16384个分片吗?
- 并非如此,如果一个分片只有一个槽位,这对于集群的数据均匀其实是难以保证的,实际上Redis作者建议集群分片数不应该超过1000.
问题二:为什么是16384个槽位?
- 节点之间通过⼼跳包通信.,⼼跳包中包含了该节点持有哪些 slots.,这个是使⽤位图这样的数据结构表⽰的.;表⽰ 16384 (16k) 个 slots,需要的位图⼤⼩是 2KB,如果给定的 slots 数更多了,⽐如 65536 个了,此时就需要消耗更多的空间,8 KB 位图表⽰了,8 KB,对于内存来说不算什么,但是在频繁的⽹络⼼跳包中,还是⼀个不⼩的开销;
- 另⼀⽅⾯,Redis 集群⼀般不建议超过 1000 个分⽚,所以 16k 对于最⼤ 1000 个分⽚来说是⾜够⽤的,同时也会使对应的槽位配置位图体积不⾄于很⼤.
集群中的所有节点,都会周期性的使用心跳包进行通信
至此,B就彻底被判定为故障节点了.
某个或者某些节点宕机,有的时候会引起整个集群都宕机(称为fail状态),以下情况会出现集群宕机:
上述例子中,B故障,并且A把B FAIL 的消息告知集群中的其他节点
所谓故障迁移,就是指把从节点提拔成主节点,继续给整个Redis集群提供支持,具体流程如下:
上述选举的过程,称为Raft算法,是一种在分布式系统中广泛使用的算法,在随机休眠时间的加持下,基本上就是谁先唤醒谁谁就能竞选成功.
扩容是一个在开发中比较常遇到的场景,随着业务的发展,现有集群很可能无法容日益增长的数据,此时给集群中加入更多新的机器,就可以使存储空间更大了.
第一步:把新的主节点加入到集群
第二步:重现分配slots
在搬运key的过程中,对于那些不需要搬运的key,访问的时候是没有任何问题的,但是对于需要搬运的key,进行访问可能会出现短暂的访问错误(key的位置发生了变化),随着搬运完成,这样的错误自然就恢复了
第三步:给新的主节点添加从节点
光有主节点了,此时扩容的目标已经初步达成,但是为了保证集群可用性,还需要给这个新的主节点添加从节点,保证该主节点宕机之后,有从节点能够顶上
扩容是比较常见的,但是缩容其实非常少见,此处简单说一下缩容的流程:
第一步:删除从节点
第二步:重新分配slots
第三步:删除主节点