节点
节点就是一个运行在集群模式下的Redis服务器,Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开启服务器的集群模式。
每个节点都会使用一个clusterNode结构来记录自己的状态,并为集群中的所有其他节点(主从节点)都创建一个相应的clusterNode结构,以此来记录其他节点的状态。
具体步骤:
1、节点A会为jiedianB创建一个clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面;
2、节点A将根据CLUSTER MEET命令给定的IP地址和端口号,向节点B发送一条MEET消息;
3、如果一切顺利,节点B将接收到节点A发送的MEET消息,节点B会为节点A创建一个clusterNode结构,并将该结构添加到自己的clusterState.nodes字典里面,然后节点B向节点A返回一条PONG消息;
4、如果一切顺利,节点A将接收到节点B返回的PONG消息,通过这条PONG消息节点A可以知道节点B已经成功地接收到自己发送地MEET消息,然后节点A将向节点B返回一条PING消息;
5、如果一切顺利,节点B将接收到节点A返回地PING消息,通过这条PING消息节点B可以知道节点A已经成功地接收到了自己返回的PONG消息,握手完成。
之后节点A会将节点B的信息通过Gossip协议传播给集群中的其他节点,让其他节点也与节点B进行握手,最终经过一段时间之后,节点B会被集群中的所有节点认识。
槽指派
Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽,数据库中的每个键都属于这16384个槽的其中一个,当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。
clusterNode结构的slots属性的numslot属性记录了节点负责处理哪些槽;slots属性是一个二进制位数组,这个数组的长度为16384/8=2048个字节,共包含16384个二进制位。如果slots数组在索引i上的二进制位的值为1,表示节点负责处理槽i;如果slots数组在索引i上的二进制位的值为0,表示节点不负责处理槽i。
numslots属性则记录节点负责处理的槽的数量,即是slots数组中值为1的二级制位的数量。
集群中的每个节点都会将自己的slots数组通过消息发送给集群中的其他节点,并且每个接收到slots数组的节点都会将数组保存到相应节点的clusterNode结构里面,因此集群中的每个节点都会知道数据库中的16384个槽分别被指派给了集群中的哪些节点。
clusterState.slots数组记录了集群中所有槽的指派信息,而clusterNode.slots数组只记录了clusterNode结构所代表的节点的槽指派信息。当程序需要将某个节点的槽指派信息通过消息发送给其他节点时,程序只需要将相应节点的clusterNode.slots数组整个发送出去;当程序要检查槽i是否已经被指派,又或者取得负责处理槽i的节点,只需要访问clusterState.slots[i]的值即可,这个操作的复杂度仅为O(1)。
集群中的重新分片
Redis集群的重新分片操作是由Redis的集群管理软件redis-trib负责执行的,Redis提供了进行重新分片的所需的所有命令,而redis-trib则通过向源节点和目标节点发送命令来进行重新分片操作。
对单个槽slot进行重新分片的步骤如下(涉及多个槽,则对每个给定的槽执行以下步骤):
1、redis-trib对目标节点发送CLUSTER SETSLOT<slot>IMPORTING<source_id>命令,让目标节点准备好从源节点导入(import)属于槽slot的键值对。
2、redis-trib对源节点发送CLUSTER SETSLOT<slot>MIGRATING<target_id>命令,让源节点准备好将属于槽slot的键值对迁移(migrate)至目标节点。
3、redis-trib向源节点发送CLUSTER GETKEYSINSLOT<slot><count>命令,获得最多count个属于槽slot的键值对的键名(key name)。
4、对于步骤3获得的每个键名,redis-trib都向源节点发送一个MIGRATE<target_ip><target_port><key_name>0<timeout>命令,将被选中的键原子地从源节点迁移至目标节点。
5、重复执行步骤3和步骤4,直到源节点保存的所有属于槽slot的键值对都被迁移至目标节点为止。每次迁移键的过程如图17-24所示。
6、redis-trib向集群中的任意一个节点发送CLUSTER SETSLOT<slot>NODE<target_id>命令,将槽slot指派给目标节点,这一指派信息会通过消息发送至整个集群,最终集群中的所有节点都会知道槽slot已经指派给了目标节点。
故障转移
当一个从节点发现自己正在复制的主节点进入了已下线状态时,从节点将开始对下线主节点进行故障转移,以下是故障转移的执行步骤:
1、复制下线主节点的所有从节点里面,会有一个从节点被选中。
2、被选中的从节点会执行SLAVEOF no one命令,成为新的主节点。
3、新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。
4、新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽。
5、新的主节点开始接收和自己负责处理的槽有关的命令请求,故障转移完成。
接下来将选举新的节点,以下是集群选举新的主节点的方法:
1、集群的配置纪元是一个自增计数器,它的初始值为0。
2、当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增一。
3、对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票。
4、当从节点发现自己正在复制的主节点进入已下线状态时,从节点会向集群广播一条CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票。
5、如果一个主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示这个主节点支持从节点成为新的主节点。
6、每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自己收到了多少条这种消息来统计自己获得了多少主节点的支持。
7、如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1张支持票时,这个从节点就会当选为新的主节点。
8、因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有N个主节点进行投票,那么具有大于等于N/2+1张支持票的从节点只会有一个,这确保了新的主节点只会有一个。
9、如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。
这个选举新主节点的方法是基于Raft算法的领头选举(leader election)方法来实现的。
消息
集群中的各个节点通过发送消息来进行通信,节点发送的消息主要有以下五种:
MEET消息:当发送者接到客户端发送的CLUSTER MEET命令时,发送者会向接收者发送MEET消息,请求接收者加入到发送者当前所处的集群里面。
PING消息:集群里的每个节点默认每隔一秒钟就会从已知节点列表中随机选出五个节点,然后对这五个节点中最长时间没有发送过PING消息的节点发送PING消息,以此来检测被选中的节点是否在线。除此之外,如果节点A最后一次收到节点B发送的PONG消息的时间,距离当前时间已经超过了节点A的cluster-node-timeout选项设置时长的一半,那么节点A也会向节点B发送PING消息,这可以防止节点A因为长时间没有随机选中节点B作为PING消息的发送对象而导致对节点B的信息更新滞后。
PONG消息:当接收者收到发送者发来的MEET消息或者PING消息时,为了向发送者确认这条MEET消息或者PING消息已到达,接收者会向发送者返回一条PONG消息。另外,一个节点也可以通过向集群广播自己的PONG消息来让集群中的其他节点立即刷新关于这个节点的认识,例如当一次故障转移操作成功执行之后,新的主节点会向集群广播一条PONG消息,以此来让集群中的其他节点立即知道这个节点已经变成了主节点,并且接管了已下线节点负责的槽。
FAIL消息:当一个主节点A判断另一个主节点B已经进入FAIL状态时,节点A会向集群广播一条关于节点B的FAIL消息,所有收到这条消息的节点都会立即将节点B标记为已下线。
PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令。
分享学习历程,坚持一周一篇原创,欢迎一起关注学习
我现在坚持一定会有收获