Redis主从模式流程场景示例

场景1:模拟一次完整的主从同步过程,机器A为master,对机器B输入slave of IPA portA命令,让机器B成为机器A的master,机器B支持EOF(redis2.8版本的client没有capa,3.0版本的client只有EOF)

  1. replicaofCommand 中会设置机器B的 server.masterhost 为机器A的IP地址 server.masterport为机器A的port, server.repl_state = REPL_STATE_CONNECT; connectWithMaster(); connectWithMaster会 server.repl_state = REPL_STATE_CONNECTING; 生成套接字,绑定 syncWithMaster写事件

  2. syncWithMaster 会同步发送ping,发送账号,密码,slave的port IP capa(eof, psync2,master会在 replconfCommand 中为 这个client指定 eof 和 psync2选项),然后调用 slaveTryPartialResynchronization ,由于此时没有cache_master,机器B发送PSYNC ? -1给机器A

  3. master 在 syncCommand 中处理psync命令,由于不能部分重同步,开始准备全同步,设置slave状态为 SLAVE_STATE_WAIT_BGSAVE_START,将这个client加入 server.slaves 队列,随机生成一个 server.replid,创建 replbacklog(server.master_repl_offset 初始化为0,server.repl_backlog_histlen 初始化为0,server.repl_backlog_off = server.master_repl_offset - server.repl_backlog_histlen + 1, server.repl_backlog_off因此为1 )

    1. 由于此时没有BGSAVE(background RDB),且slave支持EOF,master可以采用diskless方式,需要等待replicationCron触发

    2. 如果此时有diskless方式的BGSAVE,不管slave是否支持EOF,slave此时都只能等待,子进程完成BGSAVE后,主进程会在serverCron中调用checkChildrenDone,通过replicationStartPendingFork判断是否需要开启BGSAVE

    3. 如果此时有disk方式的BGSAVE,且有一个slave(slaveA)已经处于SLAVE_STATE_WAIT_BGSAVE_END,将slaveA的输出缓冲复制一份到当前slave,将当前slave的状态设为 SLAVE_STATE_WAIT_BGSAVE_END,向其发送 +FULLRESYNC server.replid slave->psync_initial_offset(slaveA当时FULLRESYNC命令的offset),然后在server.slaves队列等待BGSAVE结束

  4. replicationCron函数调用 replicationStartPendingFork(还会在checkChildrenDone 中调用) 判断是否要触发background RDB,判断条件是 有slave处于 SLAVE_STATE_WAIT_BGSAVE_START状态 ,如果有一个slave不支持EOF,master只能用RDB方式,可以立即启动BGSAVE,否则master可以采用EOF方式,需要等slave的最大等待时间超过阈值才能触发BGSAVE ),如果slave等待时间已经足够长,replicationStartPendingFork函数调用 startBgsaveForReplication 开始进行diskless方式的BGSAVE,将server.slaves中状态为 SLAVE_STATE_WAIT_BGSAVE_START 的slave的状态改为 SLAVE_STATE_WAIT_BGSAVE_END,向其发送+FULLRESYNC server.replid server.master_repl_offset(0) , 创建一个rdb子进程,一个非阻塞数据pipe(子进程将RDB数据发送给主进程,子进程随机生成一个EOF,并在RDB数据的开始和结束发送EOF,主进程通过对数据pipe注册读事件处理器,将从数据pipe收到的数据同步发送给所有slave,如果发送给某个slave时出现了partial write,需要为这个slave注册写事件,等这批数据发送给所有slave后,才从数据pipe读取下一批数据)和一个阻塞的控制pipe(子进程传输完后close数据pipe, 主进程收完全部数据后,close控制pipe通知子进程可以退出)

  5. master在生成RDB或者向slave发送RDB数据过程中,如果收到写命令,会在 propagate函数中(feedAppendOnlyFile 和 replicationFeedSlaves) 将命令追加到 AOF缓冲区、aof重写缓冲区、slave发送缓冲区(slave状态是 SLAVE_STATE_WAIT_BGSAVE_START不会添加,addReply只有在client状态是SLAVE_STATE_ONLINE且 repl_put_online_on_ack是0时才会设置写事件)和 主从复制缓冲区(只要 repl_backlog 不为NULL就添加), 主从复制缓冲区是环形缓冲,有三个重要参数master_repl_offset是最新数据(含)的全局下标,repl_backlog_histlen是有效数据大小, 开始时 <repl_backlog_size,但 buffer 满后一直 =repl_backlog_size,repl_backlog_off 是最老数据(含)的全局下标 (server.repl_backlog_off = server.master_repl_offset - server.repl_backlog_histlen + 1)

  6. master的RDB子进程把数据都发完后退出,master在 checkChildrenDone(serverCron函数调用)中 调用 updateSlavesWaitingBgsave,由于master采用的是EOF方式,会将状态为 SLAVE_STATE_WAIT_BGSAVE_END 的slave 的状态改为 SLAVE_STATE_ONLINE; 设置slave->repl_put_online_on_ack = 1; (等待slave的 REPLCONF ACK 回复)

  7. slave将master发过来的replicaID和offset 原封不动存入 server.master_replid 和 server.master_initial_offset(0),丢弃cache_master 信息,disconnectSlaves(); freeReplicationBacklog(强制让slave与自己全同步),修改状态为 REPL_STATE_TRANSFER,为连接绑定 readSyncBulkPayload 读事件:

    1. 从master接收数据,判断master是否采用EOF方式,如果是保存EOF标志,如果不是保存rdb文件大小

    2. slave根据配置判断是否用diskless还是rdb方式,机器B支持EOF也可以用rdb方式

    3. rdb方式:{

      1. 将master发过来的数据write到RDB文件中,时不时fysnc下,每次最多read 16K数据,然后return,等下次套接字可读时再处理,避免阻塞

      2. 检测传输是否结束:
        EOF:每次read数据后判断最后40字节是否和EOF标志一样,传输结束后把最后40字节数据截断掉;
        非EOF:设置读的数据总大小,如果达到就主动退出

      3. 异步清空数据库

      4. 最后fysnc下,与aof一样,先rename,再close,rename不会阻塞,close会,替换掉旧的RDB文件

      5. 调用rdbLoad载入RDB文件,载入过程中会时不时调用processEventsWhileBlocked(注意此时server.loading为1)处理文件事件,和向master发送换行符

      }

    4. diskless方式 {

      1. 备份数据库数据

      2. 一边read数据,一边加载到内存中,载入过程中会时不时调用processEventsWhileBlocked(注意此时server.loading为1)处理文件事件,和向master发送换行符

      3. 检测传输是否结束:
        EOF:读的时候不设置读的总大小,而是利用RDB文件本身的EOF标志退出,退出后再read CONFIG_RUN_ID_SIZE(40字节)大小的数据,校验是否是EOF标志
        非EOF:设置读的数据总大小,如果达到就主动退出

      4. 异步清空之前备份数据库

      }

    5. 生成server.master,为连接绑定readQueryFromClient

    6. server.master_repl_offset(0) = server.master->reploff = server.master_initial_offset(0); server.replid = server.master->replid = server.master_replid, 调用 createReplicationBacklog初始化replbacklog(server.master_repl_offset(0) 是master传过来的,server.repl_backlog_histlen 初始化为0,server.repl_backlog_off = server.master_repl_offset - server.repl_backlog_histlen + 1, server.repl_backlog_off因此为1 )

    7. slave设置 server.repl_state = REPL_STATE_CONNECTED; 如果master是EOF方式,slave 使用 addReply发送 REPLCONF ACK server.master->reploff(0) 命令给master

  8. slave收完所有RDB数据后,由于master采用EOF方式,向 master发送 REPLCONF ACK server.master->reploff(0)

  9. master在 replconfCommand中响应,如果发现 checkChildrenDone还没有执行,会先执行 checkChildrenDone修改slave状态为SLAVE_STATE_ONLINE,并设置repl_put_online_on_ack 为1 ,然后发现 slave->repl_put_online_on_ack 为1 会调用 putSlaveOnline 设置 slave->repl_put_online_on_ack = 0; 为slave绑定 sendReplyToClient函数,将slave发送缓冲的数据发给slave(可以使用多线程IO写)

  10. slave在 readQueryFromClient 中处理master发过来的数据,为了减少主从同步时延,不会使用多线程IO读。readQueryFromClient 先read数据,设置 c->read_reploff += nread,每解析并处理完一条命令后,会在 commandProcessed中更新 c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos; 然后把刚刚处理过的命令放入自己的replbacklog和自己的slave的发送缓冲中, 然后继续下一次解析和执行。addReply不会将正常命令的回复发送给master(如果client含有CLIENT_MASTER标志,直接return)

  11. 在正常同步过程中,replicationCron会让slave向master周期发送 REPLCONF ACK server.master->reploff(使用 addReply),master会据此更新slave的 repl_ack_off,而master会向slave周期性发送ping命令(使用 addReply)

场景2:模拟一次完整的主从同步过程,机器A为master,对机器B输入slave of IPA portA命令,让机器B成为机器A的master,机器B不支持EOF(redis2.8版本的client没有capa,3.0版本的client只有EOF)

  1. (与场景1一致)replicaofCommand 中会设置机器B的 server.masterhost 为机器A的IP地址 server.masterport为机器A的port, server.repl_state = REPL_STATE_CONNECT; connectWithMaster(); connectWithMaster会 server.repl_state = REPL_STATE_CONNECTING; 生成套接字,绑定 syncWithMaster写事件

  2. syncWithMaster 会发送ping,发送账号,密码,slave的port IP(redis2.8的client没有capa),然后调用 slaveTryPartialResynchronization ,由于此时没有cache_master,机器B发送PSYNC ? -1给机器A

  3. master 在 syncCommand 中处理psync命令,由于不能部分重同步,开始准备全同步,设置slave状态为 SLAVE_STATE_WAIT_BGSAVE_START,将这个client加入 server.slaves 队列,随机生成一个 server.replid,创建 replbacklog(server.master_repl_offset 初始化为0,server.repl_backlog_histlen 初始化为0,server.repl_backlog_off = server.master_repl_offset - server.repl_backlog_histlen + 1, server.repl_backlog_off因此为1 )

    1. 由于此时没有BGSAVE(background RDB),且slave不支持EOF,master只能采用disk方式,调用rdbSaveBackground立即启动BGSAVE,将server.slaves中状态为 SLAVE_STATE_WAIT_BGSAVE_START 的slave的状态改为 SLAVE_STATE_WAIT_BGSAVE_END,向其发送+FULLRESYNC server.replid server.master_repl_offset(0)

    2. (与场景1一致)如果此时有diskless方式的BGSAVE,slave只能等待,子进程完成BGSAVE后,主进程会在serverCron中调用checkChildrenDone,通过replicationStartPendingFork判断是否需要开启BGSAVE

    3. (与场景1一致)如果此时有disk方式的BGSAVE,且有一个slave已经处于SLAVE_STATE_WAIT_BGSAVE_END,将那个slave的输出缓冲复制一份到当前slave,将当前slave的状态设为 SLAVE_STATE_WAIT_BGSAVE_END,向其发送 +FULLRESYNC server.replid slave->psync_initial_offset(那个slave当时FULLRESYNC命令的offset),然后在server.slaves队列等待BGSAVE结束

  4. (与场景1一致)master在生成RDB或者向slave发送RDB数据过程中,如果收到写命令,会在 propagate函数中(feedAppendOnlyFile 和 replicationFeedSlaves) 将命令追加到 AOF缓冲区、aof重写缓冲区、slave发送缓冲区(slave状态是 SLAVE_STATE_WAIT_BGSAVE_START不会添加,addReply只有在client状态是SLAVE_STATE_ONLINE且 repl_put_online_on_ack是0时才会设置写事件)和 主从复制缓冲区(只要 repl_backlog 不为NULL就添加), 主从复制缓冲区是环形缓冲,有三个重要参数master_repl_offset是最新数据(含)的全局下标,repl_backlog_histlen是有效数据大小, 开始时 <repl_backlog_size,但 buffer 满后一直 =repl_backlog_size,repl_backlog_off 是最老数据(含)的全局下标 (server.repl_backlog_off = server.master_repl_offset - server.repl_backlog_histlen + 1)

  5. 子进程生成RDB文件后退出,master在 checkChildrenDone(serverCron函数调用)中 调用 updateSlavesWaitingBgsave,由于master采用的是disk方式,会将状态为 SLAVE_STATE_WAIT_BGSAVE_END 的slave 的状态改为 SLAVE_STATE_SEND_BULK; 设置slave->repl_put_online_on_ack = 0; (不用等待slave的 REPLCONF ACK 回复),打开RDB文件,向这些slave发送 $\r\n,为每个slave设置 sendBulkToSlave写处理器(每次最多发送16K数据就return),每个slave单独读RDB文件并发送,slave之间互相不影响,不像diskless方式,slave之前是互相影响的,RDB文件发送完成后,调用 putSlaveOnline,设置slave状态为 SLAVE_STATE_ONLINE; 绑定 sendReplyToClient写事件,将slave发送缓冲的数据发给slave(可以使用多线程IO写)

  6. (与场景1一致)slave将master发过来的replicaID和offset 原封不动存入 server.master_replid 和 server.master_initial_offset(0),丢弃cache_master 信息,disconnectSlaves(); freeReplicationBacklog(强制让slave与自己全同步),修改状态为 REPL_STATE_TRANSFER,为连接绑定 readSyncBulkPayload 读事件:

    1. 从master接收数据,判断master是否采用EOF方式,如果是保存EOF标志,如果不是保存rdb文件大小

    2. slave自己判断是否用diskless还是rdb方式,机器B不支持EOF也可以采用diskless方式

    3. rdb方式:{

      1. 将master发过来的数据write到RDB文件中,时不时fysnc下,每次最多read 16K数据,然后return,等下次套接字可读时再处理,避免阻塞

      2. 检测传输是否结束:
        EOF:每次read数据后判断最后40字节是否和EOF标志一样,传输结束后把最后40字节数据截断掉;
        非EOF:设置读的数据总大小,如果达到就主动退出

      3. 异步清空数据库

      4. 最后fysnc下,与aof一样,先rename,再close,rename不会阻塞,close会,替换掉旧的RDB文件

      5. 调用rdbLoad载入RDB文件,载入过程中会时不时调用processEventsWhileBlocked(注意此时server.loading为1)处理文件事件,和向master发送换行符

      }

    4. diskless方式 {

      1. 备份数据库数据

      2. 一边read数据,一边加载到内存中,载入过程中会时不时调用processEventsWhileBlocked(注意此时server.loading为1)处理文件事件,和向master发送换行符

      3. 检测传输是否结束:
        EOF:读的时候不设置读的总大小,而是利用RDB文件本身的EOF标志退出,退出后再read CONFIG_RUN_ID_SIZE(40字节)大小的数据,校验是否是EOF标志
        非EOF:设置读的数据总大小,如果达到就主动退出

      4. 异步清空之前备份数据库

      }

    5. 生成server.master,为连接绑定readQueryFromClient

    6. server.master_repl_offset(0) = server.master->reploff = server.master_initial_offset(0); server.replid = server.master->replid = server.master_replid, 调用 createReplicationBacklog初始化replbacklog(server.master_repl_offset(0) 是master传过来的,server.repl_backlog_histlen 初始化为0,server.repl_backlog_off = server.master_repl_offset - server.repl_backlog_histlen + 1, server.repl_backlog_off因此为1 )

    7. slave设置 server.repl_state = REPL_STATE_CONNECTED; 如果master是EOF方式,slave 使用 addReply发送 REPLCONF ACK server.master->reploff(0) 命令给master

7.(与场景1一致)slave在 readQueryFromClient 中处理master发过来的数据,为了减少主从同步时延,不会使用多线程IO读。readQueryFromClient 先read数据,设置 c->read_reploff += nread,每解析并处理完一条命令后,会在 commandProcessed中更新 c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos ( c->reploff 是slave已经处理过的数据offset); 然后把刚刚处理过的命令放入自己的replbacklog和自己的slave的发送缓冲中, 然后继续下一次解析和执行。addReply不会将正常命令的回复发送给master(如果client含有CLIENT_MASTER标志,直接return)

8.(与场景1一致)在正常同步过程中,replicationCron会让slave向master周期发送 REPLCONF ACK server.master->reploff(使用 addReply),master会据此更新slave的 repl_ack_off,而master会向slave周期性发送ping命令(使用 addReply)

场景3:紧跟场景1,机器B的网路突然中断(假设机器B的server.master -> reploff是15),随后恢复,触发部分重同步

  1. master 在 replicationCron检测到slave超时,调用 freeClient(slave); 将将这个slave从 server.monitors 和 server.slaves 队列中删除

  2. slave在replicationCron检测到server.master超时,调用freeClient(server.master); 进一步调用 replicationCacheMaster 构造 server.cache_master,清空server.master : server.master->read_reploff (15) = server.master->reploff(15); server.cached_master = server.master; server.master = NULL server.repl_state = REPL_STATE_CONNECT; 调用 connectWithMaster 尝试重连,connectWithMaster会 server.repl_state = REPL_STATE_CONNECTING; 生成套接字,绑定 syncWithMaster写事件

  3. syncWithMaster 会发送ping,发送账号,密码,slave的port IP capa(eof, psync2,master会在 replconfCommand 中为 这个client指定 eof 和 psync2选项),然后调用 slaveTryPartialResynchronization ,由于此时cache_master不为NULL ,从cached_master中构造 PSYNC replicaId offset发给master,其中psync_replid = server.cached_master->replid; psync_offset(16,表示下一个想要的offset是16) = server.cached_master->reploff + 1

  4. master 在 syncCommand 中处理psync命令, 调用 masterTryPartialResynchronization 判断是否可以部分重同步,部分重同步成功的条件:

    1. 如果master_replid和server.replid相同,且 server.repl_backlog_off <= psync_offset && psync_offset <= server.master_repl_offset + 1;

    2. 如果master_replid和server.replid2相同,且psync_offset <= server.second_replid_offset && server.repl_backlog_off <= psync_offset && psync_offset <= server.master_repl_offset + 1;

    • 假设此时 server.repl_backlog_off = 10, server.master_repl_offset = 19,server.repl_backlog_histlen = 10,部分重同步成功,为slave设置 CLIENT_SLAVE标志,状态设置为SLAVE_STATE_ONLINE; repl_put_online_on_ack = 0;由于slave支持psync2,向slave发送 +CONTINUE server.replid,否则发送 +CONTINUE,然后通过addReply把 backlog 中 psync_offset下标(含)到 server.master_repl_offset(含)之间的数据复制到slave发送缓冲,并将slave加入到pending_write队列,将在beforeSleep中发送给slave
  5. slave在syncWithMaster中接收到 CONTINUE server.replid回复,比较master返回的replidId是否与 cached_master->replid一致,由于是一致的,调用 replicationResurrectCachedMaster ,server.master = server.cached_master; server.cached_master = NULL; 为 server.master注册readQueryFromClient读事件,将其状态修改为 REPL_STATE_CONNECTED

failover场景: 主机A以前有两个slave,分别是B和C,现在A出故障,需要提升机器B为新的master,机器A和C将成为机器B的slave C<---- A ----> B failover-> C<---- B ----> A

场景4:紧跟场景1,psync2协议的failover场景,这个场景是没有数据丢失的,但代价是mastet在短时间内不能接受写命令

  1. 用户对机器A输入FAILOVER TO hostB命令(可以不指定host),进入failoverCommand处理,首先验证hostB参数的有效性(在server.slaves队列中遍历,找到和入参地址相同的,处于SLAVE_STATE_ONLINE状态的slave),然后设置 server.failover_state = FAILOVER_WAIT_FOR_SYNC; 调用 pauseClients: 设置server.client_pause_type = CLIENT_PAUSE_WRITE; server.client_pause_end_time = LLONG_MAX;

  2. 对于机器A,将server.client_pause_type 设为 CLIENT_PAUSE_WRITE后, 过期键不会删除,内存如果不够了,也不会清理数据释放空间(需要保证master的数据是constant的,见checkClientPauseTimeoutAndReturnIfPaused函数的使用场景)在 processCommand 中,如果非slave的client发送写命令,会将 client->bpop.timeout 设为 0,调用blockClient阻塞这个client(此时这条写命令已经解析好了,但是不能执行):为client加上 CLIENT_BLOCKED,CLIENT_PENDING_COMMAND标志,将client加入server.paused_clients队列,这将导致readQueryFromClient中只read,不会解析和执行命令,因此主机A的offset不会再增加

  3. 机器A会在beforeSleep 和 replicationCron会调用 updateFailoverStatus(void):检查主机B的slave->repl_ack_off(slave通过周期向master发送REPLCONF ACK server.master->reploff更新)是否等于 server.master_repl_offset(如果最开始failover命令没有指定机器,则从slave队列中找到第一个slave->repl_ack_off 等于 server.master_repl_offset的slave作为新的master),如果相等,设置 server.failover_state = FAILOVER_IN_PROGRESS; 调用 replicationSetMaster(hostB)生成server.cache_master用于主从同步,同时清空server.master:设置主机A的server.masterhost为hostB,中止正在进行的主从同步的过程,调用 replicationCacheMasterUsingMyself 构造 server.cache_master,清空server.master:生成server.cached_master,为连接绑定readQueryFromClient;server.cached_master->read_reploff = server.cached_master->reploff = server.master_initial_offset = server.master_repl_offset; server.cached_master->replid = server.replid; server.master = NULL; server.repl_state = REPL_STATE_CONNECT; connectWithMaster();

  4. 机器A的connectWithMaster会 server.repl_state = REPL_STATE_CONNECTING; 生成套接字,绑定 syncWithMaster写事件。syncWithMaster 会同步发送ping,发送账号,密码,port IP capa(eof, psync2),然后调用 slaveTryPartialResynchronization ,由于此时有cache_master,且 server.failover_state = FAILOVER_IN_PROGRESS,从cached_master中构造 PSYNC replicaId offset FAILOVER 发给机器B,其中psync_replid = server.cached_master->replid; psync_offset = server.cached_master->reploff + 1

  5. 机器B 在 syncCommand 中处理psync命令,如果是psync failover命令,且其中的replid 和 自己的 server.replid相等,调用 replicationUnsetMaster: 释放和清空server.master,server.cached_master,中止正在进行的主从同步过程,server.repl_state = REPL_STATE_NONE; 调用 disconnectSlaves 中断和自己的slave的连接,调用 shiftReplicationId : server.replid2 = server.replid; server.second_replid_offset = server.master_repl_offset+1; 重新随机生成 server.replid。然后进入 masterTryPartialResynchronization中,由于此时 master_replid和server.replid2相同,且psync_offset == server.second_replid_offset && psync_offset == server.master_repl_offset + 1;部分重同步成功,为slave(机器A)设置 CLIENT_SLAVE标志,状态设置为SLAVE_STATE_ONLINE; repl_put_online_on_ack = 0;由于slave支持psync2,向slave发送 +CONTINUE server.replid,否则发送 +CONTINUE,由于此时主机A的数据和主机B的数据一致,主机B没有数据要发送给主机A

  6. slave(机器A)在syncWithMaster中接收到 CONTINUE newReplid回复,由于master返回的newReplid 与 cached_master->replid不一致,设置 server.replid2 = cached_master->replid,server.second_replid_offset = server.master_repl_offset+1; (为了确保这台机器的slave(chained slave)后续能与这台机器部分重同步成功),server.replid = server.cached_master->replid = newReplid,然后调用 replicationResurrectCachedMaster ,server.master = server.cached_master; server.cached_master = NULL; 为 server.master注册readQueryFromClient读事件,将其状态修改为 REPL_STATE_CONNECTED,由于此时 server.failover_state == FAILOVER_IN_PROGRESS且部分重同步成功,调用 clearFailoverState:修改 server.failover_state = NO_FAILOVER; 调用 unpauseClients:设置 server.client_pause_type = CLIENT_PAUSE_OFF; 对于server.paused_clients队列中的client,调用unblockClient:将client从server.paused_clients队列删除,去掉CLIENT_BLOCKED标志,为client加上CLIENT_UNBLOCKED标志,然后加入server.unblocked_clients队列,此时如果这个client再发送请求,readQueryFromClient就会触发命令的read、解析、执行

  7. 为了避免client没有发送请求,导致刚刚unblock的client输入缓冲的命令无法及时处理的场景,在beforeSleep中主动调用 processUnblockedClients:对于 server.unblocked_clients队列的client,取消其CLIENT_UNBLOCKED标志,从队列中删除,解析和执行输入缓冲中已经存在的数据。注意,这个时候主机A已经成为slave,默认slave是read-only的,此时执行输入缓冲中的写命令会拒绝。主机A如果执行client输入缓冲中已经接收的数据,会造成主机A(slave)的数据和主机B(master)不一样?在failover过程中client都不应该继续将主机A发送请求?

  8. 对主机C执行slave of hostB命令命令,主机C在replicaofCommand中调用 replicationSetMaster(hostB)生成server.cache_master用于主从同步,同时清空server.master:在 freeClient中调用 replicationCacheMaster 构造 server.cache_master,清空server.master(当前机器是slave,换一个master ):清空server.master的所有发送缓冲、接收缓冲,resetClient(server.master); server.master->read_reploff = server.master->reploff;, server.cached_master = server.master; server.master = NULL;server.repl_state = REPL_STATE_CONNECT; 设置新的 server.masterhost,中止正在进行的主从同步的过程,connectWithMaster(); connectWithMaster会 server.repl_state = REPL_STATE_CONNECTING; 生成套接字,绑定 syncWithMaster写事件

  9. 主机C的syncWithMaster 会发送ping,发送账号,密码,slave的port IP capa(eof, psync2,master会在 replconfCommand 中为 这个client指定 eof 和 psync2选项),然后调用 slaveTryPartialResynchronization ,由于此时cache_master不为NULL ,从cached_master中构造 PSYNC replicaId offset发给master,其中psync_replid = server.cached_master->replid; psync_offset = server.cached_master->reploff + 1

  10. master 在 syncCommand 中处理psync命令, 调用 masterTryPartialResynchronization 判断是否可以部分重同步,部分重同步成功,为slave设置 CLIENT_SLAVE标志,状态设置为SLAVE_STATE_ONLINE; repl_put_online_on_ack = 0;由于slave支持psync2,向slave发送 +CONTINUE server.replid,否则发送 +CONTINUE,然后通过addReply把 backlog 中 psync_offset下标(含)到 server.master_repl_offset(含)之间的数据复制到slave发送缓冲,并将slave加入到pending_write队列,将在beforeSleep中发送给slave

  11. slave(机器C)在syncWithMaster中接收到 CONTINUE newReplid回复,由于master返回的newReplid 与 cached_master->replid不一致,设置 server.replid2 = cached_master->replid,server.second_replid_offset = server.master_repl_offset+1; (为了确保这台机器的slave(chained slave)后续能与这台机器部分重同步成功),server.replid = server.cached_master->replid = newReplid,然后调用 replicationResurrectCachedMaster ,server.master = server.cached_master; server.cached_master = NULL; 为 server.master注册readQueryFromClient读事件,将其状态修改为 REPL_STATE_CONNECTED。

  12. 如果机器C的部分重同步失败,将进行全同步,丢弃cache_master 信息,此时如果有其他以前机器A的slave尝试与机器C进行部分重同步(作为机器C的slave)将失败

在新版的PSYN2.0中,相比原来的PSYN功能,最大的变化支持两种场景下的部分重同步,一个场景是slave提升为master后,其他slave可以从新提升的master进行部分重同步。另外一个场景就是slave重启后,可以进行部分重同步。

在具体实现上redis服务器在生成RDB文件的时候会把当前的复制ID和复制偏移量一起保存到RDB文件中,后面服务器重启的时候,就可以从RDB文件中读取复制ID和复制偏移量,从而可以进行部分重同步功能。另外当服务器从slave提升为master后,会保存两个复制ID,其他slave复制的时候可以根据第二个复制ID来进行部分重同步。

posted @ 2022-09-01 11:33  zoo-keeper  阅读(113)  评论(0)    收藏  举报