目录
前言
redis哨兵能够提高系统的可用性,但是真正存储数据的还是master和slave节点,所有的数据都保存在单个master和slave节点中
如果数据量很大,接近超出了master/slave所在的机器的物理内存,就会出现严重问题!
众所周知,redis的数据是保存在内存上的 ,即便现在的大公司的内存已经能够达到TB级别,但是在现在的这种大数据环境下,显然是不够的
Redis集群应运而生,引入多组Master/Slave,每一组Master/Slave存储数据全集的一部分,从而构成一个更大的整体,称为Redis集群(Cluster)
- 上图三个不同的redis的主从结构分别存储的数据集3TB的1/3
- 每一个红框都可以是一个分片(Sharding)
数据分片算法
Redis cluster 的核心思路是用多组机器来存数据的每个部分,那么接下来的核心问题就是,给定一个数据(一个具体的key),将他分配到应该去到的分片上
哈希求余:
设有N个分片[0,N-1] 进行编号
假设一共有三个分片,那么得到key后将他hash之后%3 得到分片编号~
优点:
简单高效、分配均匀
缺点:
一旦需要进行扩容,N发生了改变,那么原有的映射规则都被破坏了,需要将所有数据进行重新排列,以满足新的映射规则,此时需要搬运的数据量很大~~
一致性hash算法:
为了降低上述的搬运开销, 能够更⾼效扩容, 业界提出了 "⼀致性哈希算法"
第一步:把[0,2^32-1] 这个数据空间,映射到一个圆环上,数据按照顺时针方向增长。
第二步:假设当前存在三个分片,就把分片放到圆环的某个位置上
第三步:假设此时有一个key,那么hash得到的值为H,那么将这个key映射到哪一个分片呢?
即从H位置顺时针往下找,直到找到的第一个分片,就是该key所属的分片
那么在这种一致性hash的情况下,如果扩容一个分片,如何处理呢?
原有分片不动,在环上安排一个新的分片位置即可
此时,只需要把0号分片上的部分数据,搬运给3号分片即可,1号和2号分片管理的区间都是不变的
优点:
大大降低了扩容时数据搬运的规模,提高了扩容操作的效率
缺点:
数据分配不均匀(有的多有的少,数据倾斜)
哈希槽分区算法(Redis使用)
为了解决上述问题,Redis cluster引入了哈希槽(hash slots)算法
hash_slot = crc16(key) % 16384
| crc16 也是一种hash算法
| 16384 其实是16 ^ 1024 = 2 ^ 14
相当于将所有key值映射到 16384 个槽位上,也就是[0,16383]
然后再把这些槽位均匀的分配给每个分片,每个分片的节点都需要记录自己持有哪些分片
假设当前有三个分片,一种可能的分配方式:
- 0 号分⽚: [0,5461], 共 5462 个槽位
- 1号分⽚: [5462,10923],共5462个槽位
- 2号分⽚: [10924,16383],共5460个槽位
TIPS:这里的分片规则是很灵活的,每个分片所持有的槽位也不一定连续
每个分片的节点使用位图来记录自己拥有哪些槽位,对于16384的槽位来说,需要2048个字节(2KB)大小的内存空间表示
此时扩容增加了一个3号分片,即可以针对原有的槽位进行重新分配。
即可以把原有分片所持有的槽位分给新的分片。
- 0 号分⽚:[0,4095],共4096 个槽位
- 1 号分⽚:[5462,9557],共 4096 个槽位
- 2 号分⽚:[10924,15019],共 4096 个槽位
- 3 号分⽚:[4096,5461]+ [9558, 10923] + [15019, 16383], 共 4096 个槽位?
TIPS:实际使用的时候,不需要手动指定哪些槽位分配给某个分片,只需要告诉某个分片应该持有多少槽位即可,Redis会做单完成分配和搬运~~
拓展问题:
Q1:Redis集群最多只有16384个分片吗?
A1:nonono,实际上redis作者建议集群分片不要超过1000. 更何况如果你是用16000以上的那么大个规模的集群,本身的可用性也是一大问题
Q2:为什么是16384个槽位?
A2:这里直接翻译Redis原作者的话,大致是如下。
- 节点之间的通讯是通过心跳包机制的,而心跳包是频繁的发送,心跳包又包含了该节点的各种信息包括所持有的槽。当槽的个数是16384的时候,位图的大小为2kb,但如果槽的个数是65535的时候,此时的位图大小将来到恐怖的8kb,虽然这对于内存不算什么,但是在网络中,仍然是一个不晓得开销
- 另一方面,Redis集群一般不会超过1000个分片,所以16k对于最大1000个分片的下的情景是足够使用的,同时对应的槽的体积也不会很大