Redis源码解析:27集群(三)主从复制、故障转移

本文详细介绍了Redis集群中的主从复制和故障转移机制。主从复制确保数据冗余和集群稳定性,而故障转移在主节点下线时,通过选举产生新的主节点,保证服务不间断。文中探讨了纪元、投票流程和配置更新等关键步骤,揭示了Redis高可用性的实现方式。
摘要由CSDN通过智能技术生成

一:主从复制

         在集群中,为了保证集群的健壮性,通常设置一部分集群节点为主节点,另一部分集群节点为这些主节点的从节点。一般情况下,需要保证每个主节点至少有一个从节点。

         集群初始化时,每个集群节点都是以独立的主节点角色而存在的,通过向集群节点发送”CLUSTER  MEET     <ip> <port>”命令,可以使集群节点间相互认识。节点间相互认识之后,可以通过向某些集群节点发送"CLUSTER  REPLICATE  <nodeID>"命令,使收到命令的集群节点成为<nodeID>节点的从节点。

         在函数clusterCommand中,处理这部分的代码如下:

    else if (!strcasecmp(c->argv[1]->ptr,"replicate") && c->argc == 3) {
        /* CLUSTER REPLICATE <NODE ID> */
        clusterNode *n = clusterLookupNode(c->argv[2]->ptr);

        /* Lookup the specified node in our table. */
        if (!n) {
            addReplyErrorFormat(c,"Unknown node %s", (char*)c->argv[2]->ptr);
            return;
        }

        /* I can't replicate myself. */
        if (n == myself) {
            addReplyError(c,"Can't replicate myself");
            return;
        }

        /* Can't replicate a slave. */
        if (nodeIsSlave(n)) {
            addReplyError(c,"I can only replicate a master, not a slave.");
            return;
        }

        /* If the instance is currently a master, it should have no assigned
         * slots nor keys to accept to replicate some other node.
         * Slaves can switch to another master without issues. */
        if (nodeIsMaster(myself) &&
            (myself->numslots != 0 || dictSize(server.db[0].dict) != 0)) {
            addReplyError(c,
                "To set a master the node must be empty and "
                "without assigned slots.");
            return;
        }

        /* Set the master. */
        clusterSetMaster(n);
        clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
        addReply(c,shared.ok);
    } 

         "CLUSTER  REPLICATE"命令的格式是"CLUSTER  REPLICATE  <nodeID>";

         首先,根据命令参数<nodeID>,从字典server.cluster->nodes中寻找对应的节点n;如果找不到n,或者,如果n就是当前节点,或者,n节点是个从节点,则回复客户端错误信息后返回;

         如果当前节点为主节点,则当前节点不能有负责的槽位,当前节点的数据库也必须为空,如果不满足以上任一条件,则将不能置当前节点为从节点,因此回复客户端错误信息后,直接返回;

         接下来,调用clusterSetMaster函数置当前节点为n节点的从节点,最后,回复客户端"OK";

 

         clusterSetMaster函数的代码如下:

void clusterSetMaster(clusterNode *n) {
    redisAssert(n != myself);
    redisAssert(myself->numslots == 0);

    if (nodeIsMaster(myself)) {
        myself->flags &= ~REDIS_NODE_MASTER;
        myself->flags |= REDIS_NODE_SLAVE;
        clusterCloseAllSlots();
    } else {
        if (myself->slaveof)
            clusterNodeRemoveSlave(myself->slaveof,myself);
    }
    myself->slaveof = n;
    clusterNodeAddSlave(n,myself);
    replicationSetMaster(n->ip, n->port);
    resetManualFailover();
}

         首先,必须保证n不是当前节点,而且当前节点没有负责任何槽位;

         如果当前节点已经是主节点了,则将节点标志位中的REDIS_NODE_MASTER标记清除,并增加REDIS_NODE_SLAVE标记;然后调用clusterCloseAllSlots函数,置server.cluster->migrating_slots_to和server.cluster->importing_slots_from为空;

         如果当前节点为从节点,并且目前已有主节点,则调用clusterNodeRemoveSlave函数,将当前节点从其当前主节点的slaves数组中删除,解除当前节点与其当前主节点的关系;

         然后,置myself->slaveof为n,调用clusterNodeAddSlave函数,将当前节点插入到n->slaves中;

         然后,调用replicationSetMaster函数,这里直接复用了主从复制部分的代码,相当于向当前节点发送了"SLAVE  OF"命令,开始主从复制流程;

         最后,调用resetManualFailover函数,清除手动故障转移状态;

 

二:故障转移

1:纪元(epoch)

         理解Redis集群中的故障转移,必须要理解纪元(epoch)在分布式Redis集群中的作用,Redis集群使用RAFT算法中类似term的概念,在Redis集群中这被称之为纪元(epoch)。纪元的概念在介绍哨兵时已经介绍过了,在Redis集群中,纪元的概念和作用与哨兵中的纪元类似。Redis集群中的纪元主要是两种:currentEpoch和configEpoch。

 

         a、currentEpoch

         这是一个集群状态相关的概念,可以当做记录集群状态变更的递增版本号。每个集群节点,都会通过server.cluster->currentEpoch记录当前的currentEpoch。

         集群节点创建时,不管是主节点还是从节点,都置currentEpoch为0。当前节点接收到来自其他节点的包时,如果发送者的currentEpoch(消息头部会包含发送者的currentEpoch)大于当前节点的currentEpoch,那么当前节点会更新currentEpoch为发送者的currentEpoch。因此,集群中所有节点的currentEpoch最终会达成一致,相当于对集群状态的认知达成了一致。

 

         currentEpoch作用在于,当集群的状态发生改变,某个节点为了执行一些动作需要寻求其他节点的同意时,就会增加currentEpoch的值。目前currentEpoch只用于从节点的故障转移流程,这就跟哨兵中的sentinel.current_epoch作用是一模一样的。

         当从节点A发现其所属的主节点下线时,就会试图发起故障转移流程。首先就是增加currentEpoch的值,这个增加后的currentEpoch是所有集群节点中最大的。然后从节点A向所有节点发包用于拉票,请求其他主节点投票给自己,使自己能成为新的主节点。

         其他节点收到包后,发现发送者的currentEpoch比自己的currentEpoch大,就会更新自己的currentEpoch,并在尚未投票的情况下,投票给从节点A,表示同意使其成为新的主节点。

 

         b、configepoch

         这是一个集群节点配置相关的概念,每个集群节点都有自己独一无二的configepoch。所谓的节点配置,实际上是指节点所负责的槽位信息。

         每一个主节点在向其他节点发送包时,都会附带其configEpoch信息,以及一份表示它所负责槽位的位数组信息。而从节点向其他节点发送包时,包中的configEpoch和负责槽位信息,是其主节点的configEpoch和负责槽位信息。节点收到包之后,就会根据包中的configEpoch和负责槽位信息,记录到相应节点属性中。

 

         configEpoch主要用于解决不同的节点的配置发生冲突的情况。举个例子就明白了:节点A宣称负责槽位1,其向外发送的包中,包含了自己的configEpoch和负责槽位信息。节点C收到A发来的包后,发现自己当前没有记录槽位1的负责节点(也就是server.cluster->slots[1]为NULL),就会将A置为槽位1的负责节点(server.cluster->slots[1]= A),并记录节点A的configEpoch。后来,节点C又收到了B发来的包,它也宣称负责槽位1,此时,如何判断槽位1到底由谁负责呢?这就是configEpoch起作用的时候了,C在B发来的包中,发现它的configEpoch,要比A的大,说明B是更新的配置,因此,就将槽位1的负责节点设置为B(server.cluster->slots[1] = B)。

 

         在从节点发起选举,获得足够多的选票之后,成功当选时,也就是从节点试图替代其下线主节点,成为新的主节点时,会增加它自己的configEpoch,使其成为当前所有集群节点的configEpoch中的最大值。这样,该从节点成为主节点后,就会向所有节点发送广播包,强制其他节点更新相关槽位的负责节点为自己。

        

2:故障转移概述

         集群中,当某个从节点发现其主节点下线时,就会尝试在未来某个时间点发起故障转移流程。具体而言就是先向其他集群节点发送CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST包用于拉票,集群主节点收到这样的包后,如果在当前选举纪元中没有投过票,就会向该从节点发送CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK包,表示投票给该从节点。从节点如果在一段时间内收到了大部分主节点的投票,则表示选举成功,接下来就是升级为主节点,并接管原主节点所负责的槽位,并将这种变化广播给其他所有集群节点,使它们感知这种变化,并修改自己记录的配置信息。

         接下来,就是故障转移中各个环节的详细描述:

 

3:从节点的选举和升级

         3.1、从节点发起故障转移的时间

         从节点在发现其主节点下线时,并不是立即发起故障转移流程,而是要等待一段时间,在未来的某个时间点才发起选举。这个时间点是这样计算的:

mstime() + 500ms + random()%500ms + rank*1000ms

         其中,固定延时500ms,是为了留出时间,使主节点下线的消息能传播到集群中其他节点,这样集群中的主节点才有可能投票;随机延时是为了避免两个从节点同时开始故障转移流程;rank表示从节点的排名,排名是指当前从节点在下线主节点的所有从节点中的排名,排名主要是根据复制数据量来定,复制数据量越多,排名越靠前,因此,具有较多复制数据量的从节点可以更早发起故障转移流程,从而更可能成为新的主节点。

         rank主要是通过调用clusterGetSlaveRank得到的,该函数的代码如下:

int clusterGetSlaveRank(void) {
    long long myoffset;
    int j, rank = 0;
    clusterNode *master;

    redisAssert(nodeIsSlave(myself));
    master = myself->slaveof;
    if (master == NULL) return 0; /* Never called by slaves without master. */

    myoffset = replicationGetSlaveOffset();
    for (j = 0; j < master->numslaves; j++)
        if (master->slaves[j] != myself &&
            master->slaves[j]->repl_offset > myoffset) rank++;
    return rank;
}</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值