Redis源碼閱讀 (主從同步)

6. 主從同步

Redis支持主從同步,其官方文檔 http://www.redis.io/topics/

replication 簡要介紹了它的主從同步機制。翻譯官方文檔的文章, Redis的主從同步

有以下特點:

•一個Master可以有多個Slave

•Slave可以接受其它Slave的連接,因此Masters和Slaves之間可以行程一個網絡

•同步時,作爲Master的一端(可能其狀態是Slave)的同步是非阻塞的,它可以繼

續處理命令;作爲Slave一端的第一次同步是阻塞的。

•主從機制可以用於提高擴展性,比如可以用多個Slaves處理複雜的讀請求。

•可以通過Slave備份數據,以降低Master的負載。

6.1. 建立連接

接下來我們看Redis中Slave和Master是如何建立連接的。 Redis通過

server.replstate表明Master和Slave的狀態,初始時,狀態均爲

REDIS_REPL_NONE。 server.masterhost和server.masterport爲Master的地址,

Master結點的這兩個變量爲NULL。

Slave的server.replstate狀態除了REDIS_REPL_NONE外,還有

/* 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_TRANSFER 2 /* Receiving .rdb from master */

#define REDIS_REPL_CONNECTED 3 /* Connected to master */

Master對於每個Slave都維護一個狀態,在結構體redisClient.replstate中,

因爲Slave對於Master,其本質也是一個客戶端。 Master對每個Slave標記狀態,有四

種:

/* 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 */

REDIS_REPL_WAIT_BGSAVE_START表示Master準備dump數據到磁盤,任何新建

的Slave連接均處於該狀態; Master開始數據dump後,將Slave的狀態修改爲

REDIS_REPL_WAIT_BGSAVE_END,當dump完成後,通知Slave,狀態變爲

REDIS_REPL_SEND_BULK,準備向其發送數據;當發送完成, Slave就緒後,狀態變爲

REDIS_REPL_ONLINE,至此Master可以向狀態爲REDIS_REPL_ONLINE的Slave發送更

新命令。

對於Slave,如果server.replstate!=REDIS_REPL_CONNECTED,則無法處理命令(獲取Slave狀態的命令除外);對於Master,如果c.replstate!=REDIS_REPL_ONLINE,也不會向它發送命令。serverCron()中每隔10秒會調用replicationCron(),處理主從同步相關的功能。 replicationCron()會檢測同數據傳輸超時, Slave與Master連接斷開等問題。在分析可能的故障前,先分析建立連接的過程。

6.1.1. Master

Redis啓動時可以通過配置文件指定爲Master狀態或Slave狀態, Slave也可以通過Sync命令與另一個Master(可能是Master,也可能是與Master有連接的Slave)建立主從連接。下面分析syncCommand()的處理。首先,如果發送sync命令的結點已經是Slave狀態,則無需再次同步。如果接受sync指令的結點,本身也是Slave,且沒有與任何Master建立連接,則無法響應sync命令。如果Slave與Master之間還有其它交互命令沒有處理完,也無法繼續響應sync命令。然後Master開始檢測bgsave狀態,如果已經有bgsave進程,再判斷是否正在響應其它Slave的sync命令,且正在dump數據(Slave的replstate爲REDIS_REPL_BGSAVE_END),則直接將新Slave的replstate狀態置爲REDIS_REPL_BGSAVE_END,並準備將dump好的數據傳給新Slave。如果沒有dump好的數據,則將新Slave的狀態置爲REDIS_REPL_BGSAVE_START,並等待bgsave進程完成,之後再啓動bgsave,爲Slave dump數據。如果沒有bgsave進程,則啓動新的bgsave進程,並將Slave的狀態置爲REDIS_REPL_BGSAVE_END。最後,將新Slave加入Master的server.slaves列表。

void syncCommand(redisClient *c) {

/* ignore SYNC if aleady slave or in monitor mode */

if (c->flags & REDIS_SLAVE) return;

/* Refuse SYNC requests if we are a slave but the link with our master

* is not ok... */

if (server.masterhost && server.replstate != REDIS_REPL_CONNECTED) {

addReplyError(c,"Can't SYNC while not connected with my master");

return;

}

/* SYNC can't be issued when the server has pending data to send to

* the client about already issued commands. We need a fresh reply

* buffer registering the differences between the BGSAVE and the current

* dataset, so that we can copy to other slaves if needed. */

if (listLength(c->reply) != 0) {

addReplyError(c,"SYNC is invalid with pending input");

return;

}

redisLog(REDIS_NOTICE,"Slave ask for synchronization");

/* Here we need to check if there is a background saving operation

* in progress, or if it is required to start one */

if (server.bgsavechildpid != -1) {

/* Ok a background save is in progress. Let's check if it is a good

* one for replication, i.e. if there is another slave that is

* registering differences since the server forked to save */

redisClient *slave;

listNode *ln;

listIter li;

listRewind(server.slaves,&li);

while((ln = listNext(&li))) {

slave = ln->value;

if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) break;

}

if (ln) {

/* Perfect, the server is already registering differences for

* another slave. Set the right state, and copy the buffer. */

listRelease(c->reply);

c->reply = listDup(slave->reply);

c->replstate = REDIS_REPL_WAIT_BGSAVE_END;

redisLog(REDIS_NOTICE,"Waiting for end of BGSAVE for SYNC");

} else {

/* No way, we need to wait for the next BGSAVE in order to

* register differences */

c->replstate = REDIS_REPL_WAIT_BGSAVE_START;

redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC");

}

} else {

/* Ok we don't have a BGSAVE in progress, let's start one */

redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC");

if (rdbSaveBackground(server.dbfilename) != REDIS_OK) {

redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE");

addReplyError(c,"Unable to perform background save");

return;

}

c->replstate = REDIS_REPL_WAIT_BGSAVE_END;

}

c->repldbfd = -1;

c->flags |= REDIS_SLAVE;

c->slaveseldb = 0;

listAddNodeTail(server.slaves,c);

return;

}

如前文Snapshot快照分析,當bgsave進程備份完成時,在serverCron()中會調用backgroundSaveDoneHandler()處理,該函數中調用updateSlavesWaitingBgsave()處理等待bgsave的Slave結點。

updateSlavesWaitingBgsave()檢查server.slaves列表,當發現狀態爲REDIS_REPL_WAIT_BGSAVE_START的結點時,會再次啓動bgsave進程進行dump數據。當發現狀態爲REDIS_REPL_WAIT_BGSAVE_START的結點時,會準備將數據發送給Slave,並將Slave的狀態修改爲REDIS_REPL_SEND_BULK。 Master在Slave上監聽WRITEABLE事件,當Slave可寫時,調用sendBulkToSlave()將數據發送到Slave。在Master中, sendBulkToSlave()將數據從磁盤讀入,通過socket發送到Slave,完成後刪除WRITEABLE事件,並將Slave狀態改爲REDIS_REPL_ONLINE。然後再次監聽Slave的WRITEABLE事件,當Slave可寫時,表明Slave已經準備好開始同步指令。

6.1.2. Slave

Redis可以在配置文件中指定爲Slave模式,並指定Master的地址,這樣的Slave結點啓動時即爲REDIS_REPL_ONLINE狀態,然後在replicationCron()中調用syncWithMaster()與Master同步。syncWithMaster()中,向Master發送sync命令,並準備接收數據的目錄。然後通過readSyncBulkPayload()處理Master發送的dump的數據。 Slave通過syncRead()和syncWrite()與Master傳輸數據,這兩個方法底層是異步IO的,但封裝成阻塞型,因此Slave第一次同步是阻塞的。replicationCron()會定時Master的dump數據是否發送完成,如果長時間沒有收到dump數據的數據包, Slave會通過replicationAbortSyncTransfer()取消數據同步。

6.2. 指令同步

在call()中, Master會將改變了數據的命令通過replicationFeedSlaves()同步到Slaves中。 Slave以處理普通命令的流程處理這些Master發來的命令。特別地在serverCron()中, Master會刪除過期的數據, Slave則等待Master同步DEL指令將過期數據刪除。replicationCron()中, Master會定時向所有Slave發送心跳指令,同時,Slave的replicationCron()會通過Ping指令檢查Slave與Master的連接狀態,如果Slave長時間沒有收到主機的Ping,則會斷開與主機的連接。

6.3. 主從轉換

slaveof可以轉換Redis中結點的狀態。對於Slave結點, slaveof no one可以將Slave與Master斷開,並將Slave轉爲主機。對於Master, slaveof ip port可以講主機轉爲Slave。

Redis的主從轉換非常簡單,如果同步未完成,則取消同步;否則,直接改變server.masterhost等狀態。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章