0.前言
- 说明:该章节相关操作不需要记忆,理解流程和原理即可,用的时候能自主查到即可
1.基本概念
- 哨兵模式提高了系统的可用性,但是真正用来存储数据的还是
master
和slave
节点,所有的数据都需要存储在单个master
和slave
节点中- 如果数据量很大,接近超出了
master/slave
所在机器的物理内存,就可能出现严重的问题了
- 如果数据量很大,接近超出了
- 如何获取更大的空间?
- 加机器即可
- 所谓”大数据”的核心,其实就是一台机器搞不定了,用多台机器来搞定
- Redis集群就是在上述的思路下,引入多组
Master/Slave
,每组Master/Slave
存储数据全集的一部分,从而构成一个更大的整体,称为Redis集群(Cluster
) - 假定整个数据全集是1TB,引入三组
Master/Slave
来存储,那么每一组机器只需要存储整个数据全集的 1 / 3 1/3 1/3即可- 三组机器存储的数据都是不同的
- 每个
Slave
都是对应Master
的备份,当Master
挂了,对应的Slave
会补位成Master
- 每个红框部分都可以称为是一个**分片**(
Sharding
)- 如果全量数据进一步增加,只要再增加更多的分片,即可解决
- 示例:
Master1
和Slave11
和Slave12
保存的是同样的数据,占总数据的 1 / 3 1/3 1/3Master2
和Slave21
和Slave22
保存的是同样的数据,占总数据的 1 / 3 1/3 1/3Master3
和Slave31
和Slave32
保存的是同样的数据,占总数据的 1 / 3 1/3 1/3
2.数据分片算法
0.前言
- Redis Cluster的**核⼼思路是⽤多组机器来存数据的每个部分**
- 那么接下来的核⼼问题就是,给定⼀个数据(⼀个具体的
key
),那么这个数据应该存储在哪个分⽚上? 读取的时候⼜应该去哪个分片读取?
1.哈希求余
设有N个分片,使用
[0, N-1]
进行编号- 针对某个给定的
key
,先计算hash
值,再把得到的结果%N
,得到的结果即为分片编号 - 后续如果要取某个
key
的值,也是针对key
进行hash
,再对N求余,就可以找到对应的分片编号了
- 针对某个给定的
优点:简单高效,数据分配均匀
缺点:一旦需要进行扩容,N改变了,原有的映射规则被破坏,就需要让节点之间的数据互相传输,重新排列,以满足新的映射规则,此时需要搬运的数据量是比较多的,开销较大
2.一致性哈希算法
为了降低上述的搬运开销,能够更高效扩容,业界提出了”一致性哈希算法”
本质区别:
- 在哈希求余中,当前
key
属于哪个分片,是交替的 - 在一致性哈希下,把交替出现,改进成了连续出现,此时就降低了需要搬运数据的可能
- 在哈希求余中,当前
key
映射到分片序号的过程不再是简单求余了,而是改成以下过程:把 0 0 0 -> 2 32 − 1 2^{32} - 1 232−1这个数据空间,映射到一个圆环上,数据按照顺时针方向增长
假设当前存在三个分片,就把分片放到圆环的某个位置上
假定有一个
key
,计算得到hash
值H
,那么这个key
映射到哪个分片呢?从
H
所在位置,顺时针往下找,找到的第一个分片,即为该key
所从属的分片
这就相当于,N个分片的位置,把整个圆环分成了N个管辖区间,
key
的hash
值落在某个区间内,就归对应区间管理
如果扩容一个分片,如何处理呢?
- 原有分片在环上的位置不动,只要在环上新安排一个分片位置即可
- 此时只需要把0号分片上的部分数据,搬运给3号分片即可,1号分片和2号分片管理的区间都是不变的
优点:大大降低了扩容时数据搬运的规模,提高了扩容操作的效率
缺点:数据分配不均匀 -> 有的多有的少,数据倾斜
3.哈希槽分区算法(Redis使用)
- 为了解决上述问题(搬运成本高和数据分配不均匀),Redis Cluster引入了哈希槽(
hash slots
)算法 - 本质:把哈希求余和一致性哈希的
hash slots
:- 说明:
crc16
也是一种hash
算法- 16384 16384 16384是 16 ∗ 1024 16 * 1024 16∗1024,即16K,为 2 14 2^{14} 214
- 解释:
- 相当于把整个哈希值,映射到 16384 16384 16384个槽位上,也就是
[0, 16383]
- 然后再把这些槽位比较均匀的分配给每个片,每个分片的节点都需要记录自己持有哪些分片
- 相当于把整个哈希值,映射到 16384 16384 16384个槽位上,也就是
- 这里的分片规则是很灵活的,每个分片持有的槽位也不一定连续
- 每个分片的节点使用位图来表示自己持有哪些槽位,对于 16384 16384 16384个槽位而言,需要2048个字节(2KB)大小的内存空间表示
- 如果需要扩容,比如新增一个3号分片,就可以针对原有的槽位进行重新分配
- 把之前每个分片持有的槽位,各拿出一点,分给新分片
hash_slot = crc16(key) % 16384
- 说明:
- 示例:
- 假设当前有三个分片,一种可能的分配方式:
- 0号分片:
[0, 5461]
,共5462个槽位 - 1号分片:
[5462, 10923]
,共5462个槽位 - 2号分片:
[10924, 16383]
,共5460个槽位
- 0号分片:
- 此时扩容了一个分片,一种可能的分配方式:
- 0号分片:
[0, 4095]
,共4096个槽位 - 1号分片:
[5462, 9557]
,共4096个槽位 - 2号分片:
[10924, 15019]
,共4096个槽位 - 3号分片:
[4096, 5461] + [9558, 10923] + [15019, 16383]
,共4096个槽位
- 0号分片:
- 假设当前有三个分片,一种可能的分配方式:
- 在实际使用Redis集群分片的时候,不需要手动指定哪些槽位分配给某个分片,只需要告诉某个分片应该持有多少个槽位即可,Redis会自动完成后续的槽位分配,以及对应的
key
搬运的工作 - 两个问题:
- Redis集群是最多有16384个分片吗?
- 并非如此,如果一个分片一个槽位,这对于集群的数据均匀是难以保证的
- 实际上,Redis作者建议集群分片数不应该超过1000
- 并且,16000这么大规模的集群,本身的可用性也是一个大问题
- 一个系统越复杂,出现故障的概率就越高
- 为什么是16384个槽位?
- Redis作者的答案
- 节点之间通过⼼跳包通信,⼼跳包中包含了该节点持有哪些slots,这个是使⽤位图这样的数据结构表⽰的
- 表⽰16384(16k)个slots,需要的位图⼤⼩是2KB,如果给定的slots数更多了,⽐如65536个了,此时就需要消耗更多的空间,如8KB位图表⽰了
- 8KB对于内存来说不算什么,但是在频繁的⽹络⼼跳包中,还是⼀个不⼩的开销的
- 另⼀⽅⾯,Redis集群⼀般不建议超过1000 个分⽚,所以16k对于最⼤1000个分⽚来说是⾜够⽤的,同时也会使对应的槽位配置位图体积不⾄于很⼤
- Redis集群是最多有16384个分片吗?