在 Redis 中,用户可以通过执行 SLAVEOF
命令或者设置 slaveof
选项,让一个服务器去复制(replicate)另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave)。
REDIS_REPL_CONNECT
// redis.h |
server.repl_state
的默认值是 REDIS_REPL_NONE
,执行 SLAVEOF
命令之后,进入 REDIS_REPL_CONNECT
状态,到这里命令就返回了。
REDIS_REPL_CONNECTING
// redis.c |
先来看看 Redis 的入口函数,在其中调用了initServer()
方法,这个方法里面将 serverCron()
方法注册为一个时间事件的回调函数,即 1 毫秒之后就会触发执行,之后通过 1000/server.hz
这个返回值控制 1 秒运行 server.hz
次,原理就是在事件处理函数 processTimeEvents()
中会把这个返回值设置为下次触发距离现在的毫秒数,而宏 run_with_period
控制 replicationCron()
函数 1 秒运行一次。该函数中判断如果是 REDIS_REPL_CONNECT
状态,则与主服务器建立连接并注册文件事件回调函数 syncWithMaster
,最后进入 REDIS_REPL_CONNECTING
状态。
// replication.c |
REDIS_REPL_RECEIVE_PONG
站在从服务器的角度,与服务器建立连接成功后,会触发可写事件调用回调函数 syncWithMaster()
,因为接下来的 RDB 文件发送非常耗时,所以需要确认主服务器真的能访问,首先发送 PING
命令,主服务器响应 PONG
之后,触发可读事件再次调用该函数,读取响应确认连接没有问题。
然后从服务器调用 slaveTryPartialResynchronization()
函数,如果是首次同步则向主服务器发送 PSYNC ? -1
进行完整同步,非首次同步则发送 PSYNC <runid> <offset>
试图进行部分同步。然后回到 syncWithMaster()
函数,判断如果返回结果不是 PSYNC_CONTINUE
则表示要进行完整同步:打开一个临时文件用于保存主服务传过来的 RDB 文件数据,并设置可读回调函数 readSyncBulkPayload()
准备读取主服务器发送过来的 RDB 文件数据,进入 REDIS_REPL_TRANSFER
状态。
// replication.c |
REDIS_REPL_TRANSFER
readSyncBulkPayload()
方法主要用来读取主服务器发送过来的 RDB 文件数据,并定期刷写到磁盘临时文件中,等接收完毕之后重命名为 dump.rdb,首先把数据库清空,然后载入新的 RDB 文件,最后将主服务器设置成自己的一个客户端,准备接收来自主服务器的命令传播,并更新状态为 REDIS_REPL_CONNECTED
。
// replication.c |
再来看主服务器是怎样响应 PSYNC
命令的,syncCommand
就是 SYNC
和 PSYNC
命令的实现函数:如果当前有 BGSAVE
在执行且有至少一个 slave 在等待,如果没有 BGSAVE
在执行则开始一个新的,主服务器在这两种情况下都会进入 REDIS_REPL_WAIT_BGSAVE_END
状态;如果本次 BGSAVE
不能用,则进入 REDIS_REPL_WAIT_BGSAVE_START
状态。
// replication.c |
在定时任务 serverCron()
中,如果检测到 BGSAVE
执行完毕,则可以进入到 updateSlavesWaitingBgsave()
函数:如果在 REDIS_REPL_WAIT_BGSAVE_END
状态下则开始读取并发送 RDB 文件给 slave,REDIS_REPL_WAIT_BGSAVE_START
状态下则需要执行一个新的 BGSAVE
,完成后同样回到该函数发送 RDB 文件,发送完成后主服务器进入 REDIS_REPL_ONLINE
状态。
// redis.c |
REDIS_REPL_CONNECTED
在完整同步执行完成之后,主从服务器的数据库将达到一致的状态,但当主服务器执行新的写命令后,可能会导致主从服务器状态不再一致。为了让主从状态再次回到一致状态,主服务器需要对从服务器进行命令传播操作:主服务器将自己执行的写命令,发送给从服务器执行。在 processCommand()
函数中用调用 call()
函数执行命令,如果是一个写命令,会调用 propagate()
函数进行命令传播,具体的实现在 replicationFeedSlaves()
函数中:构建命令内容、备份到 backlog、将内容发送给各个从服务器。
// redis.c |
当从服务器在断线重连后,向主服务器发送 PSYNC <runid> <offset>
命令,主服务器在 masterTryPartialResynchronization()
函数中判断是否可以进行增量同步:如果 backlog 中包含所有需要同步的命令,主服务器返回 +CONTINUE
表示可以进行增量同步,随后将 backlog 中的命令同步给从服务器,否则返回 +FULLRESYNC <runid> <offset>
进行完整同步。
// replication.c |