Redis源码解析:15Resis主从复制之从节点流程

本文详细介绍了Redis主从复制的从节点流程,包括同步、命令传播、完全重同步和部分重同步。从节点在接收到SLAVEOF命令或配置时,会与主节点进行建链、握手过程,尝试部分重同步或完全重同步。在完全重同步中,从节点接收RDB数据并加载以保持与主节点一致。主从复制过程中,从节点状态不断转换,确保数据的一致性和高可用性。
摘要由CSDN通过智能技术生成

         Redis的主从复制功能,可以实现Redis实例的高可用,避免单个Redis 服务器的单点故障,并且可以实现负载均衡。

 

一:主从复制过程

         Redis的复制功能分为同步(sync)和命令传播(commandpropagate)两个操作:

         同步操作用于将从节点的数据库状态更新至主节点当前所处的数据库状态;

         命令传播操作则用于在主节点的数据库状态被修改,导致主从节点的数据库状态不一致时,让主从节点的数据库重新回到一致状态;

   

1:同步

         当客户端向从节点发送SLAYEOF命令,或者从节点的配置文件中配置了slaveof选项时,从节点首先需要执行同步操作,也就是将从节点的数据库状态更新至主节点当前所处的数据库状态。

         在Redis2.8版本之前,从节点对主节点的同步操作,是通过从节点向主节点发送SYNC命令来完成。过程如下:

         a:从节点向主节点发送SYNC命令;

         b:主节点收到SYNC命令后,执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。

         c:当主节点的BGSAVE命令执行完毕时,主节点会将生成的RDB文件发送给从节点,从节点接收并载人这个 RDB文件,将自己的数据库状态更新至主服务器执行BGSAYE命令时的状态。

         d:主节点将记录在缓冲区里面的所有写命令发送给从节点,从节点执行这些写命令,将自己的数据库状态更新至主节点数据库当前所处的状态。

 

2:命令传播

         在同步操作执行完毕之后,主从服务器两者的数据库将达到一致状态。但当主节点执行客户端发送的写命令时,主从服务器状态将不再一致。

         为了让主从服务器再次回到一致状态,主服务器将自己执行的写命令,发送给从服务器执行,当从服务器执行了相同的写命令之后,主从服务器将再次回到一致状态。

 

3:完全重同步和部分重同步

         以上就是旧版Redis执行主从复制时的过程。它有个缺点,就是当主从节点间的连接断开后,从节点会发送SYNC命令来重新进行一次完整复制操作。这样即使断开期间主节点的变化很小(甚至没有),也需要将主节点中的所有数据重新快照并传送一次。这种实现方式显然不太理想。

         自2.8版开始,Redis支持部分重同步功能。该功能通过”PSYNC”命令实现。部分重同步是基于如下3点实现的:

         a:从节点会保存主节点的运行ID。每个Redis 运行实例均会拥有一个唯一的运行ID,每当实例重启后,就会自动生成一个新的运行ID。

         b:在命令传播阶段,主节点每将一个命令传送给从节点时,都会同时把该命令存放到一个积压队列(backlog)中,并记录下当前积压队列中,存放的命令的偏移量范围。

         c:同时,从节点接收到主节点传来的命令时,会记录下该命令的偏移量。主节点和所有从节点都记录了命令的偏移量。

 

         当主从连接准备就绪后,从节点会发送一条”PSYNC”命令,格式为”PSYNC  <runid> <offset>”。

         从节点第一次连接主节点是,置runid为”?”,offset为”-1”。如果是断链重连,则从节点发送之前保存的主节点运行ID和复制偏移。

         主节点收到”PSYNC”命令后,会执行以下判断来决定此次重连是否可以执行部分重同步:

         a:首先判断从节点传送来的<runid>是否和自己的运行ID相同;

         b:然后判断从节点传送来的复制偏移量<offset>是否在积压队列中;

         如果以上两个条件都满足,则可以执行部分重同步,并将积压队列中相应的命令发送给从节点。如果不满足,主节点会进行一次完全重同步,也就是进行之前版本中收到”SYNC”命令后的操作。

 

         主从复制功能是从节点主动发起,主节点配合完成的,因此,本文先介绍从节点在主从复制时的流程。

         注意,下面的流程都基于Redis3.0.5版本。

 

二:从节点属性

         在Redis源码中,表示Redis服务器的全局结构体struct redisServer  server中,与主从复制相关的,从节点属性如下:

         server.masterhost:记录主节点的ip地址;

         server.masterport:记录主节点的端口号;

         server.repl_transfer_s:socket描述符,用于主从复制过程中,从节点与主节点之间的TCP通信,包括主从节点间的握手通信、接收RDB数据,以及后续的命令传播过程;

         server.repl_transfer_fd:文件描述符,用于从节点将收到的RDB数据写到本地临时文件;

         server.repl_transfer_tmpfile:从节点上,用于记录RDB数据的临时文件名;

         server.repl_state:记录主从复制过程中,从节点的状态。

         server.master:当从节点接受完主节点发来的RDB数据之后,进入命令传播过程。从节点就将主节点当成一个客户端看待。server.master就是redisClient结构的主节点客户端,从节点接收该server.master发来的命令,像处理普通客户端的命令请求一样进行处理,从而实现了从节点和主节点之间的同步。

         server.master->reploff:从节点记录的复制偏移量,每次收到主节点发来的命令时,就会将命令长度增加到该复制偏移量上,以保持和主节点复制偏移量的一致。

         server.master->replrunid:从节点记录的主节点运行ID。

 

         server.cached_master:主从节点复制过程中(具体应该是命令传播过程中),如果从节点与主节点之间连接断掉了,会调用freeClient(server.master),关闭与主节点客户端的连接。为了后续重连时能够进行部分重同步,在freeClient中,会调用replicationCacheMaster函数,将server.master保存到server.cached_master。该redisClient结构中记录了主节点的运行ID,以及复制偏移。当后续与主节点的连接又重新建立起来的时候,使用这些信息进行部分重同步,也就是发送"PSYNC  <runid>  <offset>"命令。

 

         server.repl_master_runid和server.repl_master_initial_offset:从节点发送"PSYNC  <runid> <offset>"命令后,如果主节点不支持部分重同步,则会回复信息为"+FULLRESYNC <runid>  <offset>",表示要进行完全重同步,其中<runid>表示主节点的运行ID,记录到server.repl_master_runid中,<offset>表示主节点的初始复制偏移,记录到server.repl_master_initial_offset中。

 

三:建链和握手过程

         从节点在收到客户端发来的”slaveof”命令时,或者在配置文件中配置了”slaveof”选项时,就会向主节点建链,开始主从复制过程。

         在主节点将实际的RDB数据发送给从节点之前,还需要经历握手过程,这非常类似于TCP建链的三次握手。该过程由从节点主动发起,主节点作出相应的回应。握手过程如下:

         该握手过程中,从节点的状态会发生转换,从REDIS_REPL_CONNECT状态起,一直到REDIS_REPL_RECEIVE_PSYNC状态期间,都算是握手过程。

 

1:TCP建链

         在Redis源码中,使用server.repl_state记录从节点的状态。在Redis初始化时,该状态为REDIS_REPL_NONE。

         当从节点收到客户端用户发来的”SLAVEOF” 命令时,或者在读取配置文件,发现了”slaveof”配置选项,就会将server.repl_state置为REDIS_REPL_CONNECT状态。该状态表示下一步需要向主节点发起TCP建链。

         在定时执行的函数serverCron中,会调用replicationCron函数检查主从复制的状态。该函数中,一旦发现当前的server.repl_state为REDIS_REPL_CONNECT,则会调用函数connectWithMaster,向主节点发起TCP建链请求,其代码如下:

int connectWithMaster(void) {
    int fd;

    fd = anetTcpNonBlockBestEffortBindConnect(NULL,
        server.masterhost,server.masterport,REDIS_BIND_ADDR);
    if (fd == -1) {
        redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
            strerror(errno));
        return REDIS_ERR;
    }

    if (aeCreateFile
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值