前言
作者阅读了《Redis设计与实现》(第二版)replication章节。想更深地了解replication机制。于是分析了Redis 3.2.8源码。并编写了这篇文章。如有分析不对的地方,还请各位包涵,指正。
从Slave端分析Replication机制
将Redis实例设置成Slave。主要流程,见下图。
第一步在replication.c:saveofComand函数中完成。在此函数中,起主要作用的函数是replicationSetMaster。此函数保存Master的IP和端口号。并将replication的状态设置为REPL_STATE_CONNECT。此状态为Slave状态。
第二步,在replication.c:replicationCron函数中执行。此函数1秒中被调用一次。此函数检查到slave的状态为REPL_STATE_CONNECT,就开始调用函数connectWithMaster函数,创建连接Master的TCP连接。
第三步,在connectWithMaster函数中完成。在此函数中,创建了连接Master的TCP。此TCP连接为异步非阻塞连接。它为此连接的可读事件和可写事件,注册了回调函数syncWithMaster。并将slave的状态,切换到正在连接的状态。状态值为REPL_STATE_CONNECING。syncWithMaster函数完成后续的同步操作。
当与Master的连接建立好后,socket可读。触发syncWithMaster函数。此函数执行了一个状态机。状态机见下图:
在发送PSYNC时,程序会调用slaveTryPartialResynchronization(fd, 0)。发送PSYNC命令。此函数根据Slave的状态发送两种PSYNC命令。当为Slave连接过Master这种情况,发送PSYNC <master run id> <repl offset>。尝试部分同步。当没有连接过Master这咱情况,发送PSYNC ? -1 尝试完全同步。
在进入接收PSYNC状态后,程序调用slaveTryPartialResynchronization(fd, 1)来解析Master的对PSYNC命令的响应。此函数有四种返回值。
当返回PSYNC_WAIT_REPLY时,表示Master还没有响应。程序在下一次响应读事件时,会继续调用此函数。
当返回PSYNC_FULLRESYNC时,表示Master 向Slave发送了全部同步的响应。在返回PSYNC_FULLRESYNC之前,此函数已经执行了此socket的可读事件属性。之后,程序将不再调用syncWithMaster。
当返回PSYNC_CONTINUE时,表示开始部分同步操作。之后,程序也不会再调用syncWithMaster了。
当返回PSYNC_NOT_SUPPORTED时,表示Master不支持PSYNC命令。程序之后会向Master发送SYNC命令。之后开始完全同步。
syncWithMaster函数调用laveTryPartialResynchronization(fd, 1)之后。若返回值为PSYNC_CONTINUE。则在replicationResurrectCachedMaster函数中,将socket的可读事件处理回调函数换成了networking.c:readQueryFromClient函数。此函数将可读事件放入事件循环中。替换完之后,Master发送来的命令,可直接进入Slave的事件循环。完成backlog的同步。同步完之后,Master会将它接收到的命令,调用replicatoinFeedSlaves函数,发送给slave,完成Master到Slave的实时同步。
syncWithMaster函数调用laveTryPartialResynchronization(fd, 1)之后。若返回值为SYNC_FULLRESYNC。则进入执行完全同步。将socket的可读事件回调函数换成readSyncBulPayload。之后将slave的状态切换成REPL_STATE_TRANSFER。
readSyncBulPayload函数会接收master生成的数据库rdb文件。接收完之后,会加载这个rdb文件。
Slave和Master之间的交互信息,见下图:
问题:开始执行完全同步到执行完,这段时间内,Master接收了一些新的命令。同步完全成之后,这些命令是怎么传递过来的?
从Master端分析Replication机制
Slave向Master发送PSYNC命令。Master使用函数syncCommand响应此命令。此函数为Master端主从复制的入口。
syncComand函数流程图如下:
其中检查是否为部分同步和完成部分同步的逻辑(a1-a2),见下图:
执行完全同步(a3)的逻辑见下图:
以上三图就是syncComand函数的主要逻辑。
A1,a2对应的主要代码为replication.c:masterTryPartialResynchronization函数。masterTryPartialResynchronization函数调用了addReplyReplicationBacklog函数,完成了C6所代指的内容。
syncCommand调用了startBgsaveForReplicatoin来执行应完全重新同步操作。syncCommand命令在开启复制数据库子进程之后,在执行了其它一些操作之后,就退出了。并没有执行发送生成的数据库文件等剩余的同步操作。在server的定时回调函数(server.c:serverCron)中,执行以下操作:
由rdb.c:backgroundSaveDoneHandler函数,执行以上操作。
在执行d4操作之后,回调函数换成sendBulkToSlave。由这个函数发送生成的rdb文件。
每当master到slave的连接可写时,都会调用sendBulkToSlave函数。此函数的流程图如下:
至此,分析完了redis主从复制机制。还有些细节,不影响分析其主体架构。就不描述了。
未清楚的地方
在发送完rdb文件后,程序是如何同步在生成rdb文件期间累积的命令的?虽然,能从代码里猜测出其实现方法。但未完全搞清楚。待日后分析。