redis源代码分析- replication

redis的复制方法和机制都比较简单。

slaveof masterip port

在slave端键入命令之后,就开启了从master到slave的复制。一个master可以有多个slave,master有变化的时候会主动的把命令传播给每个slave。slave同时可以作为其他的slave的master,前提条件是这个slave已经处于稳定状态(REDIS_REPL_CONNECTED)。slave在复制的开始阶段处于阻塞状态(sync_readline)无法对外提供服务。

数据的有向图会让redis的运维很有搞头。

slaveof no one

从slave状态的转换回master状态,切断与原master的数据同步。

下面根据图形来描述一个复制全过程。

复制的顺序是从左到右。绿色的过程表示replstate复制状态的变迁,复制相关的函数主要在src/replication.c里,红色为master的复制函数,蓝色的为slave使用的复制函数。

slave端接收到客户端的slaveof masterip ort命令之后,根据readonlyCommandTable找到对应的函数为slaveofCommand。slaveofCommand会保存masterip,masterport,并把server.replstate改成REDIS_REPL_CONNECT,然后返回给客户端OK。

slave端主线程在已经注册时间事件serverConn(redis.c 518)里执行replicationCron(redis.c 646)来开始与master的连接。调用syncWithMaster()开始与master的通信。经过校验之后,会发送一个SYNC command给master端,然后打开一个临时文件用于存储接下来master发过来的rdb文件数据。再添加一个文件事件注册readSyncBulkPayload函数,这个就是接收rdb文件的数据的函数,然后修改状态为REDIS_REPL_TRANSFER。

master接收到SYNC command后,跳转到syncCommand函数(replication.c 556)。syncCommand会调度rdbSaveBackground函数,启动一个子进程做一个全库的持久化rdb文件,并把状态改为REDIS_REPL_WAIT_BGSAVE_END。

master的主线程的serverCron会等待做持久化的子进程的退出,并判断退出的状态是否是正常。

if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
if (pid == server.bgsavechildpid) {
backgroundSaveDoneHandler(statloc);
} else {
....

如果子进程正常退出,会转到backgroundSaveDoneHandler函数。backgroundSaveDoneHandler 处理每次rdbSaveBackground成功后的收尾工作,并打开刚刚产生的rdb文件。然后注册一个sendBulkToSlave函数用于发送rdb文件,状态切换至REDIS_REPL_SEND_BULK。

sendBulkToSlave作用就是根据上面打开的rdb文件,读取并发送到slave端,当文件全部发送完毕之后修改状态为REDIS_REPL_ONLINE。

我们回到slave,上面讲到slave通过readSyncBulkPayload接收rdb数据,接收完整个rdb文件后,会清空整个数据库emptyDb()(replication.c 374)。然后就通过rdbLoad函数载入接收到的rdb文件,于是slave和master数据就一致了,再把状态修改为REDIS_REPL_CONNECTED。

接下来就是master和slave之间增量的传递的增量数据,另外slave和master在应用层有心跳检测(replication.c 543),和超时退出(replication.c 511)。

最后再给出一个replstate状态图

slave端复制状态

REDIS_REPL_NONE:未复制的状态

REDIS_REPL_CONNECT:已经接收到slaveof命令,但未发出sync命令给master

REDIS_REPL_TRANSFER:已经发出sync,但还没接收完rdb文件

REDIS_REPL_CONNECTED:已经接收完rdb文件,开始增量接收复制内容

maste端redis_client的复制状态

REDIS_REPL_WAIT_BGSAVE_START:bgsave被抢占,等待bgsave为其持久化进行工作。

REDIS_REPL_WAIT_BGSAVE_END:等待bgsave持久化rdb文件。

REDIS_REPL_SEND_BULK:rdb文件已经生成。开始拷贝给slave。

REDIS_REPL_ONLINE:rdb拷贝完毕,开始增量复制。

另外在syncCommand函数里有几种可能性

bgsave进程未启动,则启动。

bsave已启动,且已经存在另外的一个slave连接,已经由这个连接开启了bgsave,则等待bgsave完毕,共享这次的持久化。

bsave已启动,但不是由slave连接所启动的,则等待下次bgsave的退出。在updateSlavesWaitingBgsave函数里再次启动bgsave进程。

update:

9 Comments

  1. [...] bgsave命令调度rdbSaveBackground函数启动了一个子进程然后调度了rdbSave函数,子进程的退出状态由 serverCron的backgroundSaveDoneHandler来判断,这个在复制这篇文章里讲到过,这里就不罗嗦了。 [...]

  2. [...] bgsave命令调度rdbSaveBackground函数启动了一个子进程然后调度了rdbSave函数,子进程的退出状态由 serverCron的backgroundSaveDoneHandler来判断,这个在复制这篇文章里讲到过,这里就不罗嗦了。 [...]

  3. Seabird Designs 说道:

    … Hoterran.info…

    [...] Read More: hoterran.info/redis_replication [...]…

  4. kfc 说道:

    我想请问下,当一个新的slave(A)进来打算同步的时候,如果当前的bgsave是由另一个slave(B)启动,并且这个salve(B)正在等待这次bgsave结束,那么这个新的slave(A)就可以共享此次的持久化dump文件,但是在A连进来之前,有可能B已经开启bgsave一段时间了,这段时间内假如有一些内容的更新,那么对于此次的更新master是以写到reply的方式,等同步结束之后再发送给slave的,我看到代码里面,新的slave进来的时候,仅仅拷贝了slave(B)的reply过去,但是有可能某些数据还是写在slave(B)的buf里面,这时候buf里面的内容,是不是新的slave(A)就同步不到了?

  5. [...] bgsave命令调度rdbSaveBackground函数启动了一个子进程然后调度了rdbSave函数,子进程的退出状态由 serverCron的backgroundSaveDoneHandler来判断,这个在复制这篇文章里讲到过,这里就不罗嗦了。 [...]

  6. allen 说道:

    能把增量同步的代码细节展现出来就更好了…

  7. [...] 3.REDIS源代码分析- REPLICATION [...]

Leave a Reply