节点
一个Redis集群通常由多个节点(node)组成,在刚开始的时候,每个节点都是相互独立的,它们都处于一个只包含自己的集群当中,要组建一个真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。
连接各个节点的工作可以使用CLUSTERMEET命令来完成,该命令的格式如下:
CLUSTER MEET ip
向一个节点node发送CLUSTER MEET命令,可以让node节点与ip和port所指定
的节点进行握手(handshake),当握手成功时,node节点就会将ip和port所指定的节点添加到node节点当前所在的集群中。
CLUSTER MEET 命令的实现
通过向节点A发送CLUSTERRMEET命令,客户端可以让接收命令的节点A将另一个节点B添加到节点A当前所在的集群里面:
CLUSTERMEET
收到命令的节点A将与节点B进行握手(handshake),以此来确认彼此的存在,并为
将来的进一步通信打好基础
1)节点A会为节点B创建一个clusterNode结构,并将该结构添加到自己的
clusterState.nodes字典里面。
2)之后,节点A将根据CLUSTER MEET命令给定的IP地址和端口号,向节点B发送一条MEET消息(message)
3)如果一切顺利,节点B将接收到节点A发送的MEET消息,节点B会为节点A创建一个clusterNode结构,并将该结构添加到自已的clusterState.nodes字典里面。
4)之后,节点B将向节点A返回一条PONG消息。
5)如果一切顺利,节点A将接收到节点B返回的PONG消息,通过这条PONG消息节点A可以知道节点B已经成功地接收到了自己发送的MEET消息。
6)之后,节点A将向节点B返回一条PING消息。
7)如果一切顺利,节点B将接收到节点A返回的PING消息,通过这条PING消息节
点B可以知道节点A已经成功地接收到了自己返回的PONG消息,握手完成。
槽指派
Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。
当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。
传播节点的槽指派信息
一个节点除了会将自己负责处理的槽记录在clusterNode结构的siots属性和numslots属性之外,它还会将自已的slots数组通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。
因为集群中的每个节点都会将自已的s1ots数组通过消息发送给集群中的其他节点,并且每个接收到slots数组的节点都会将数组保存到相应节点的clusterNode结构里面,因此,集群中的每个节点都会知道数据库中的16384个槽分别被指派给了集群中的哪些节点。
复制与故障转移
Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。举个例子,对于包含7000、7001、7002、7003四个主节点的集群来说,我们可以将7004、7005两个节点添加到集群里面,并将这两个节点设定为节点7000的从节点,如图所示(图中以双圆形表示主节点,单圆形表示从节点)。
如果这时,节点7000进人下线状态,那么集群中仍在正常运作的几个主节点将在节点7000的两个从节点——节点7004和节点7005中选出一个节点作为新的主节点,这个新的主节点将接管原来节点7000负责处理的槽,并继续处理客户端发送的命令请求。
故障检测
集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线,如果接收PING消息的节点没有在规定的时间内,向发送PING消息的节点返回PONG消息,那么发送PING消息的节点就会将接收PING消息的节点标记为疑似下线(probable fail,PFAIL)。
举个例子,如果节点7001向节点7000发送了一条PING消息,但是节点7000没
有在规定的时间内,向节点7001返回一条PONG消息,那么节点7001就会在自己的clusterstate.nodes字典中找到节点7000所对应的clusterNode结构,并在结构的flags属性中打开REDIS NODE_PFA工L标识,以此表示节点7000进人了疑似下线状态。
集群中的各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息,例如某个节点是处于在线状态、疑似下线状态(PFAIL),还是已下线状态(FAIL)。
当一个主节点A通过消息得知主节点B认为主节点C进人了疑似下线状态时,主节点A会在自己的clusterstate.nodes字典中找到主节点C所对应的clusterNode结构,并将主节点B的下线报告(failurereport)添加到 clusterNode结构的fail_reports链表里面。
如果在一个集群里面,半数以上负责处理槽的主节点都将某个主节点报告为疑似下线那么这个主节点将被标记为已下线(FAL),将主节点x标记为已下线的节点会向集群广播一条关于主节点x的EAIL消息,所有收到这条EAIL消息的节点都会立即将主节点标记为已下线。
举个例子,主节点7002和主节点7003都认为主节点7000进人了下线状态,并且主节点7001也认为主节点7000进人了疑似下线状态(代表主节点7000的结构打开了REDIS NODEPEAIL标识),综合起来,在集群四个负责处理槽的主节点里面,有三个都将主节点7000标记为下线,数量已经超过了半数,所以主节点7001会将主节点7000标记为已下线,并向集群广播一条关于主节点7000的FAIL消息,如图所示。
故障转移
当一个从节点发现自己正在复制的主节点进人了已下线状态时,从节点将开始对下线主节点进行故障转移,以下是故障转移的执行步骤:
1)复制下线主节点的所有从节点里面,会有一个从节点被选中。
2)被选中的从节点会执行SLAVEOFnoone命令,成为新的主节点。
3)新的主节点会撤销所有对已下线主节点的槽指派,并将这些槽全部指派给自己。
4)新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经由从节点变成了主节点,并且这个主节点已经接管了原本由已下线节点负责处理的槽
5)新的主节点开始接收和自已负责处理的槽有关的命令请求,故障转移完成。
选举新的主节点
新的主节点是通过选举产生的。以下是集群选举新的主节点的方法:
1)集群的配置纪元是一个自增计数器,它的初始值为0。
2)当集群里的某个节点开始一次故障转移操作时,集群配置纪元的值会被增一。
3)对于每个配置纪元,集群里每个负责处理槽的主节点都有一次投票的机会,而第一个向主节点要求投票的从节点将获得主节点的投票。
4)当从节点发现自己正在复制的主节点进人已下线状态时,从节点会向集群广播一条CLUSTERMSG TYPE FAILOVERAUTH REQUEST消息,要求所有收到这条消息、并且具有投票权的主节点向这个从节点投票
5)如果一个主节点具有投票权(它正在负责处理槽),并且这个主节点尚未投票给其他从节点,那么主节点将向要求投票的从节点返回一条CLUSTERMSG_TYPE_FAILOVER AUTHACK消息,表示这个主节点支持从节点成为新的主节点。
6)每个参与选举的从节点都会接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根据自已收到了多少条这种消息来统计自己获得了多少主节点的支持。
7)如果集群里有N个具有投票权的主节点,那么当一个从节点收集到大于等于N/2+1张支持票时,这个从节点就会当选为新的主节点。
B)因为在每一个配置纪元里面,每个具有投票权的主节点只能投一次票,所以如果有N个主节点进行投票,那么具有大于等于N/2+1张支持票的从节点只会有一个,这确保了新的主节点只会有一个。
9)如果在一个配置纪元里面没有从节点能收集到足够多的支持票,那么集群进入一个新的配置纪元,并再次进行选举,直到选出新的主节点为止。这个选举新主节点的方法和选举领头Sentinel的方法非常相似,因为两者都是基于Raft算法的领头选举(leader election)方法来实现的。