一, 實現原理圖
(1)Slave服務器連接到Master服務器.
(2)Slave服務器發送SYCN命令.
(3)Master服務器備份數據庫到.rdb文件.
(4)Master服務器把.rdb文件傳輸給Slave服務器.
(5)Slave服務器把.rdb文件數據導入到數據庫中.
上面的這5步是同步的第一階段, 接下來在Master服務器上調用每一個命令都使用replicationFeedSlaves()來同步到Slave服務器.
二, 實現細節
(1) Slave服務器連接到Master服務器 / 發送SYNC命令:
Slave服務器通過syncWithMaster()函數來連接Master服務器(如果Master服務器需要密碼登陸的話,先登陸), 並且發送SYNC命令請求同步, 接着打開rdb文件(用於存儲由Master發送過來的數據), 創建讀rdb的IO事件(readSyncBulkPayload). 代碼如下:
int syncWithMaster(void) {
......
//登陸master服務器
if(server.masterauth) {
syncWrite(fd, "AUTH xxx\r\n", strlen(server.masterauth)+7, 5);
......
}
//發送SYNC命令
syncWrite(fd,"SYNC \r\n",7,5);
......
//打開rdb文件
dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
......
//創建讀rdb的IO事件
aeCreateFileEvent(server.el, fd, AE_READABLE, readSyncBulkPayload, NULL);
......
return REDIS_OK;
}
(2) Master服務器備份數據庫到.rdb文件:
當Slave服務器發送SYNC命令到Master服務器時, Master服務器便會調用syncCommand()函數來進行同步. 同步的第一步是把數據庫的數據存儲爲rdb文件, 存儲完畢後調用 updateSlavesWaitingBgsave()函數來發送rdb文件給所有的Slave服務器.代碼如下:
void syncCommand(redisClient *c) {
//如果正在保存rdb文件
if (server.bgsavechildpid != -1) {
......
//主要判斷當前存儲rdb文件是不是由SYNC命令觸發的
//如果當前存儲rdb文件不是由SYNC命令觸發, 則要等到下一次
......
} else {//否則調用rdbSaveBackground()存儲rdb文件
rdbSaveBackground(server.dbfilename);
}
}
當rdbSaveBackground()函數執行完畢, 就會調用updateSlavesWaitingBgsave()來發送rdb文件到所有的Slave服務器, 代碼如下:
void updateSlavesWaitingBgsave(int bgsaveerr) {
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
slave->repldbfd = open(server.dbfilename,O_RDONLY);
.......
aeCreateFileEvent(server.el,slave->fd,AE_WRITABLE, sendBulkToSlave,slave);
}
}
updateSlavesWaitingBgsave()要做的事情是, 打開rdb文件, 創建發送rdb文件IO事件(sendBulkToSlave). 而sendBulkToSlave()主要的工作就是把rdb文件發送給Slave服務器.
而當Slave服務器接收rdb文件完畢之後(readSyncBulkPayload()函數處理), 會清空原來數據庫的數據, 然後把rdb文件的數據導入到數據庫中.
(3) 增量同步
完成上面的步驟之後, 同步基本完成. 接下來的工作就是增量同步, 也就是當Master服務器有數據更新的時候, 會立刻同步到所有的Slave服務器. 由replicationFeedSlaves()函數完成.
當我們在Master服務器增減數據的時候, 就會觸發replicationFeedSlaves(), 代碼如下:
void call(redisClient *c, struct redisCommand *cmd) {
......
if ((dirty || cmd->flags & REDIS_CMD_FORCE_REPLICATION) &&
listLength(server.slaves))
replicationFeedSlaves(server.slaves,c->db->id,c->argv,c->argc);
......
}
call()函數就是當用戶執行命令的時候觸發. 而dirty表示是否有數據更新, 如果有數據更新而且slave服務器不爲空, 就執行replicationFeedSlaves().
而replicationFeedSlaves()主要做的工作就是把用戶執行的命令發送到所有的Slave服務器, 讓Slave服務器執行. 這樣就可以實施同步功能.