在 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 |