Redis的复制功能是基于内存快照即rdb的,也就是说无论使用哪种持久化机制,只要用到了复制功能,master都会产生内存快照即rdb,slave接收rdb以同步数据。Redis完成复制的源码主要分布在Replication.c(共610行中,分用于master和slave的函数,下面会详述过程。
一、状态
Redis复制时slave和master都分别是一个状态机,状态定义在
Redis.h(162~179)中,主要状态如下:
/* Slave replication state - slave side */
#define REDIS_REPL_NONE 0 /* No active replication */
#define REDIS_REPL_CONNECT 1 /* Must connect to master */
#define REDIS_REPL_CONNECTING 2 /* Connecting to master */
#define REDIS_REPL_TRANSFER 3 /* Receiving .rdb from master */
#define REDIS_REPL_CONNECTED 4 /* Connected to master */
/* Synchronous read timeout - slave side */
#define REDIS_REPL_SYNCIO_TIMEOUT 5
/* Slave replication state - from the point of view of master
* Note that in SEND_BULK and ONLINE state the slave receives new updates
* in its output queue. In the WAIT_BGSAVE state instead the server is waiting
* to start the next background saving in order to send updates to it. */
#define REDIS_REPL_WAIT_BGSAVE_START 3 /* master waits bgsave to start feeding it */
#define REDIS_REPL_WAIT_BGSAVE_END 4 /* master waits bgsave to start bulk DB transmission */
#define REDIS_REPL_SEND_BULK 5 /* master is sending the bulk DB */
#define REDIS_REPL_ONLINE 6 /* bulk DB already transmitted, receive updates */
slave和master状态转换如下图:
图1 slave状态机
图2 master状态机
二、流程
图3 复制时序图
从上图可以看出整个状态(红色部分为状态)转换流程如下:
(1)初始情况下slave和master都处于REDIS_REPL_NONE(Redis.c/initServerConfig()/1081)状态。
(2)slave从配置文件中读取或者从客户端接收到slave of指令,slave状态转换为REDIS_REPL_CONNECT(Replication.c/slaveofCommand/538)。
(3)slave在定时任务serverCron(Redis.c/906)中调用replicationCron(Replicaltion.c/547)以连接master(Replication.c/connectWith),连接成功后slave状态转换为REDIS_REPL_CONNECTING(Replication.c/connectWithMaster/486)。
(4)slave发送sync命令给master(Replication.c/syncWithMaster/426),slave状态转换为REDIS_REPL_TRANSFER(Replication.c/syncWithMaster/426)。
(5)slave打开临时rdb文件用于存储即将要发送过来的快照数据(Replication.c/syncWithMaster/436),注册事件readSyncBulkPayLoad(Replication.c/syncWithMaster/446)用于接收快照数据,然后等待master发送回内存快照文件。
(6)master收到sync命令后会跳转到syncCommand(Replication.c/83)函数,master状态转换为REDIS_REPL_BGSAVE_START(Replication.c/syncCommand/128),syncCommand函数判断是否有正在进行内存快照的子进程,如果有则等待其结束,没有则调用rdbSaveBackground(Rdb.c/685)函数立即开始内存快照,当快照完成后将master状态转换为REDIS_REPL_WAIT_BGSAVE_END(Replication.c/syncCommand/139)。
(7)master主线程的定时任务serverCron(Redis.c/906)会检测做快照的子进程是否退出(Redis.c/serverCron/853),如果退出了则调用backgroundSaveDoneHandler(Redis.c/serverCron/854)函数,backgroundSaveDoneHandler会处理一些快照后的收尾工作,然后调用updateSlavesWaitingBgsave(Replication.c/208)函数。
(8)master在函数updateSlavesWaitingBgsave中打开前面快照生成的rdb文件(Replication.c/updateSlavesWaitingBgsave/228),将master状态转换为REDIS_REPL_SEND_BULK(Replication.c/updateSlavesWaitingBgsave/236),并注册事件sendBulkToSave(Replication.c/updateSlavesWaitingBgsave/238)用于读取并发送上面打开的rdb快照数据给slave(Replication.c/148),发送完毕后将master状态转换为REDIS_REPL_ONLINE(Replication.c/sendBulkToSlave/191)。
(9)slave通过步骤5中注册的事件readSyncBulkPayload来接收master发送的rdb数据(Replication.c/275),保存到本地,待接收完成后,调用emptydb(Db.c/140)以清空整个数据库,调用rdbLoad(Rdb.c/1015)重新读取master发送过来的内存快照文件以重建内存数据结构,并将状态置为REDIS_REPL_CONNECTED(Replication.c/readSyncBulkPayload/355),slave状态机转换完成,等待增量数据。
(10)master在发送快照文件的过程中,接收的任何会改变数据集的命令都会暂时先保存在slave网络连接的发送缓存队列里(list数据结构),待快照完成后,依次发给slave。slave和master之间有心跳检测和超时退出。
三、缺陷
Redis的复制机制不支持增量复制,在slave连接master时,master需要进行内存快照,然后将整个快照数据发给slave,这会给master带来很大压力,slave接收完快照数据后会先清空数据库,再重建整个数据结构,这会导致数据大时slave同步时间非常长,所以需要注意slave和master之间的网络要非常稳定,不会闪断,否则这个过程会非常悲剧,因此,slave和master之间跨IDC机房或者南北电信都会有很大风险。另外,最好一开始就规划好slave的数量,否则,结果你懂的。。。
阅读(747) | 评论(0) | 转发(0) |