前面已经知道了集群的ASK错误与MOVED错误,前者针对重新分片过程中的转移,后者是一般槽不属于委任槽,要进行转移,前者会带有一次性表示,后者只要转移一次,以后的请求都是请求在转移的服务器上
启用哨兵模式时,当监视的主服务器挂了,会有选举机制,同理,集群也有自己的选举机制与复制故障转移
复制与故障转移
Redis的集群中分为主节点和从节点,其中主节点是用来处理槽的(此时已经做了读写分离,主服务器用来写,而从服务器只能读),而从服务器需要复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求,与哨兵模式同理,下线主节点重新上线会称为新主节点的从节点。
设置从节点
客户端向一个节点发送命令
cluster replicate <node_id>
可以让接收命令的节点成为node_id指定节点的从节点,并且开始对主节点进行复制。
过程如下
- 接收到命令的节点会首先从clusterState.nodes字典中找到node_id所对应节点的clusterNode结构
- 在clusterNode结构里面拥有一个slaveof属性,也是一个clusterNode指针,其保存的是主节点的结构
- 节点会将自己的clusterState.myself.slaveof属性指向node_id对应节点
- 然后修改clusterState.myself.flags中的属性,关闭原本的REDIS_NODE_MASTER标识(表示自己不再是一个主服务器),改为REDIS_NODE_SLAVE标识(表示这个节点已经由原来的主节点变成了从节点)
- 最后,节点根据node_id找到的clusterNode结构,通过里面的ip和port去进行SLAVEOF命令,然后进行复制,复制过程跟前面学的单机复制一样,也分为全量复制和增量复制
画图展示一下myself此时的情况
一个节点成为成为从节点,并开始复制某个主节点的这一信息会通过消息发送给集群中的所有节点,最终整个集群中的所有节点都会知道某个从节点正在复制某个主节点。
这样做是为了要更新节点信息,因为在节点结构,也就是clusterNode中,有着slaves属性和numslaves属性,分别记录着该节点的所有从节点和从节点个数
typedef clusterNode(
//...
ip,
port,
//...
flags,
//..
//主节点,如果没有就为空
slaveof
//正在复制这个主节点的从节点数量
int numslaves;
//从节点数组,每个数组项都指向正在复制主节点的从节点的clusterNode
struct clusterNode **slaves;
)clusterNode;
形成的结构如下所示
故障检测
哨兵模式的故障检测单独靠哨兵去维持,但集群里并没有哨兵,故障检测是靠每个节点去进行的
集群中的每个节点都会定期地向集群中的其他节点发送PING消息,以此来检测对方是否在线
如果接收到的PING消息的节点没有在规定的时间内,回复PONG,那么发送消息的节点就会将接收PING消息的节点标记为疑似下线(修改对应clusterNode的flags属性)。
举个栗子
假如节点7000向7001发送了一条PING消息,但7001没有在规定时间内返回PONG,那么7000就会在自己的clusterState.nodes属性里面找到7001对应的clusterNode,然后去修改里面的flags属性,修改成REDIS_NODE_PFALL标识(PFALL代表是疑似下线,而FALL代表下线了),以此来标识节点7001进入了疑似下线状态。
集群中各个节点会通过互相发送消息的方式来交换集群中各个节点的状态信息,也就是会将各个节点将自己接收到的状态信息通过消息的方式分享给其他节点。
当一个主节点A通过消息得知了主节点B认为主节点C进入了疑似下线状态时,那么主节点A就会在自己的clusterState.nodes字典中找到主节点C所对应的clusterNode结构,然后将主节点B的下线报告添加到主节点C对应的clusterNode结构的fail_reports链表里面
fail_reports链表是记录了所有节点对该节点的下线报告
struct clusterNode(
//...
//一个链表结构,记录了所有认为该节点的下线报告
list *fail_reports;
//...
)clusterNode;
下面,我们来看看下线报告是怎样的
struct clusterNodeFailReport(
//进行报告的节点,即发送下线节点的节点
clusterNode *node;
//从Node收到下线报告的时间
//前面提到过,下线报告是通过消息传播的
//这个属性就是记录最新的一次接收该下线报告的时间
//如当前时间差太久的报告会被删除的(减轻缓存压力)
mstime_t time;
)typedef clusterNodeFailReport;
举个栗子
如果主节点7001在收到主节点7002、主节点7003发送的消息后得知,主节点7002和主节点7003都认为主节点7000进入了疑似下线状态,那么主节点7001将为7000节点创建两份下线报告(链表形式)。
如果在一个集群里面,半数以上的主节点认为某个主节点X报告为疑似下线,那么这个X主节点就会被标记为下线状态(flags改为FAIL),将主节点X标记为下线状态的节点会向集群广播一条关系主节点X的FAIL消息,所有收到这条消息的主节点都会将主节点X标记为下线状态
故障转移
上面已经知道故障是怎么检测出来的,下面来看看是怎么进行故障转移的
故障转移,是靠从服务器去替代主服务器成为新的主服务器来实现的
当一个从节点发现正在复制的主服务器节点进入了以下线状态时(通过广播),从节点就要开始对下线主节点进行故障转移,执行步骤如下
- 选举出从节点
- 被选中的从节点会执行SLAVEOF no one命令,成为新的主节点
- 新的主节点撤销所有被委派给下线主节点的槽,并将这些槽全部指派给自己(从节点也有自己的clusterState,可以知道主节点的指派槽)
- 新的主节点向集群广播一条PONG消息,这条PONG消息可以让集群中的其他节点立即知道这个节点已经变成主节点了,并且已经接管了原本由已下线节点负责的槽
- 其他节点要进行对应修改这个节点的状态
- 新的主节点开始接受和自己负责处理的槽有关的命令请求,故障转移完成
选举新的主节点
新的主节点是通过选举来产生的
整体步骤如下
-
集群自己会维护一个配置纪元(也是一个计数器,用来记录选举次数的)
-
当集群里的某个节点开始进行一次故障转移操作,集群的配置纪元就要自增1
-
在每一个配置纪元下,集群里的每个负责处理槽的主节点都有一次投票机会,而该票会被投给第一个要求投票给自己的从节点
-
当从节点发现自己正在复制的主节点已经进入下线状态时,从节点是会发一条广播消息,要求所有收到这条消息的有投票权的主节点向从起点投票(发送的消息为CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST)
-
一个主节点收到请求投票信息之后,如果还未投票,就会投票给这个从节点,返回一条确认信息给从节点,表示这个主节点支持从节点成为新的主节点(返回的消息为CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK)
-
每个参与选举的从节点都会统计自己接收到的CLUSTER_TYPE_FAILOVER_AUTH_ACK消息,并根据自己收到了多少条消息来确认自己获得了多少主节点的支持
-
从节点若接收到了一半主节点+1的数量的投票数,那么这个从节点就会成为新的主节点(因为每个主节点只能投票一次,所以最多只会有1个从节点能拿到这个数量的票数)
-
如果在一个配置纪元没有一个从节点收集到足够的票数,那么就会进入新的一个配置纪元(加1),然后进行重新选举,知道选出新的主节点为止