redis的集群中的简单问题
前面讲的无论是主从复制,还是哨兵+主从模式,其中要求一个主节点,就得储存整个数据的“全集”。假如有1TB的数据需要储存,我们可以用多台机器来存,随着机器数目的增加,每个机器储存的数据量就减少了。
集群把数据分成多份,有三种分法:
1.哈希求余:
这是最简单直接的分片算法,借鉴了哈希表的基本思想,借助hash函数,把一个key映射到整数,再针对数组的长度求余,就可以得到一个数组下标。
步骤1:计算数据键key的hash值,比如用MD5,SHA等哈希函数。
步骤2:对hash值取模(hash(key)%n,其中n是节点数量),结果是数据应分配的节点索引。
比如有0,1,2三个节点。假如有哈希值100-120
但如果需要扩容时,hash(key)%n(n是节点数量)的结果会发生剧烈改变,基本上大部分的节点都会发生改变。如下图:
一共二十个数据,只有三个数据不需要搬运。如果是20亿的数据,那就要搬动17亿的数据,这是一个非常恐怖的数据。
哈希求余的优点和缺点:
优点:
实现简单:靠hash+取模,逻辑直观。
均衡性好:如果hash函数分配均匀,那么每个节点所分配的数据也是很均衡的。
缺点:
当节点n变化,hash%n的结果也会发生剧烈的改变,那么在扩容的时候,就会有大量数据迁移。服务暂时不可用。
2.一致性哈希算法:
为了避免大量数据的迁移,这就有了一致哈希算法。核心是将数据和节点映射到环形的空间中,最小化节点变动的影响。
基本原理:
1.构建哈希环:
将0-2^32-1想象成一个闭合的圆环(哈希环)
2.将节点映射到哈希环中:
3.将数据映射到环上,并找到归属节点:
数据通过hash函数映射到环上的具体位置,然后根据顺时针查找遇到的第一个分片,就是归属节点。
4.扩容时,再增加一个分片:
最开始,红色区域的包含的数据都归于0号分片,但是当扩容后,新增了3号分片之后,就不会迁移大量数据,只需要迁移绿色区域的数据给3号分片即可。
一致性哈希算法的优点和缺点:
优点:
节点变动影响小:仅影响新增节点前后的数据,无需移动全量数据。
支持动态扩容:可以按需增加节点数量。
缺点:
最大的问题就是哈希环上节点分配不均问题,导致部分节点负载过高。
3.哈希槽分区算法(redis采用的方法):
核心原理“哈希槽”:
redis集群将所有数据划分为16384个槽,然后每个键值对都会根据键名映射到对应的哈希槽上。每个哈希槽由集群中的主节点进行管理和储存。
哈希映射规则:
1.先对键使用CRC16算法计算哈希值(CRC16(key))
2.将哈希值对16384取模,(CRC16(key)%16384),得到的结果就是该键所属的哈希槽。
哈希槽和节点的关系:
集群中的每个主节点都会负责一部分哈希槽的管理和储存。
例如:
0号分片:负责【0,5461】这部分的哈希槽,总共5462个槽
1号分片:负责【5462,10923】这部分哈希槽,总共5462个槽
3号分片:负责【10924,16383】这部分哈希槽,总共5462个槽。
注意:每个分片都通过“位图”这样的数据结构,表示当前分片有什么槽位。用0/1来区分这个分片是否拥有该槽位。大概每个分片只需要2kb就够了。
扩容时:
当新增一个分片后
0号分片:【0,4095】,共4096个槽位
1号分片;【5462,9557】,共4096个槽位
2号分片:【10924,15019】,共4096个槽位
3号分片:【4096,5461】+【9558,10923】+【15019,16383】,共4096个槽位。
在上述过程中,也只有被移动的槽位对应的数据需要搬动,这样的迁移的数据不会很多。redis可以手动配置,当前某个分片包含哪些槽位。
问题1:Redis集群中最多有16384个分片吗?
极端的情况,如果每个分片只管理一个槽,那最多支持16383个分片
但实际情况中,是不会用这么多分片的,因为管理成本高,性能瓶颈,资源浪费。
Redis官方建议分片数不能超过1000个。
问题2:Redis为什么是16384个槽:
因为16384个槽的位图足够小,既能满足分片需求,又能最小化内存和网络开销。
集群故障判定:
故障判定有三个阶段:
1.疑似下线:
单个节点通过心跳包检测,发现其他节点疑似故障。
检测机制:集群中所有节点通过Gossip协议互相发送心跳消息,包含自身状态和对其他节点的感知
触发条件:当节点A超过配置时间,还未收到来自节点B的心跳响应,则节点A会将节点B标记为“疑似下线(PFAIL)”,更新本地状态,这只是主观下线,并没有客观下线。
2.故障报告:
疑似下线的状态通过Gossip协议在集群中传播,让其他节点知晓。其他节点收到消息后,若自身也疑似B节点下线,然后也会更新本地状态。若未发现异常,暂不做处理。
3.确认下线:
触发条件:如果其中一个主节点X发现,超过集群中一半以上的主节点都将节点B标记为PFAIL,则节点X会将节点B的PFAIL状态升级为FAIL。这时代表节点B客观下线。
广播通知:节点X会向所有节点广播一条“节点B已经下线(FAIL)”,然后所有节点就会改变本地的状态为FAIL。
故障迁移:
如果下线的是从节点,就不需要故障的迁移。
如果下线的是主节点,此时就由B的从节点C和D触发故障迁移了。
1.从节点会先判断自己是否有资格参选主节点。如果从节点和主节点已经太久没有通信了(此时认为从节点的数据和主节点的数据差异太大了),时间超过阈值,就会失去参选资格。
2.具有资格的节点C和D,先会休眠一段时间(500ms的挤出时间+【0,500ms】的随机时间+排名*1000ms),offset越大,排名越靠前(越小)。
3.比如节点C的休眠时间到了,C就会给其他所有集群中的节点,进行拉票操作(只有主节点才有投票资格)。
4.主节点会把自己的票投给节点C,当节点C的票数超过主节点的数目一半,节点C就会晋升成为主节点。
5.C自己负责自己的slaveof no one,节点D执行slaveof C,同时C还会把自己成为主节点的消息,同步给其他的节点,大家也会更新自己保存的集群结构信息。