redis的主從複製,主要過程如下:
(1)從服務器的時間事件函數serverCron運行replicationCron函數來發起同步請求.
replicationCron()函數位於replication.c文件:
void replicationCron(void) {
/* Non blocking connection timeout? */
if (server.masterhost &&
(server.repl_state == REDIS_REPL_CONNECTING ||
server.repl_state == REDIS_REPL_RECEIVE_PONG) &&
(time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
{
redisLog(REDIS_WARNING,"Timeout connecting to the MASTER...");
undoConnectWithMaster();
}
/* Bulk transfer I/O timeout? */
if (server.masterhost && server.repl_state == REDIS_REPL_TRANSFER &&
(time(NULL)-server.repl_transfer_lastio) > server.repl_timeout)
{
redisLog(REDIS_WARNING,"Timeout receiving bulk data from MASTER... If the problem persists try to set the 'repl-timeout' parameter in redis.conf to a larger value.");
replicationAbortSyncTransfer();
}
/* Timed out master when we are an already connected slave? */
if (server.masterhost && server.repl_state == REDIS_REPL_CONNECTED &&
(time(NULL)-server.master->lastinteraction) > server.repl_timeout)
{
redisLog(REDIS_WARNING,"MASTER time out: no data nor PING received...");
freeClient(server.master);
}
/* Check if we should connect to a MASTER */
if (server.repl_state == REDIS_REPL_CONNECT) { //如果狀態爲REDIS_REPL_CONNECT則準備與主服務器建立連接
redisLog(REDIS_NOTICE,"Connecting to MASTER...");
if (connectWithMaster() == REDIS_OK) {
redisLog(REDIS_NOTICE,"MASTER <-> SLAVE sync started");
}
}
/* If we have attached slaves, PING them from time to time.
* So slaves can implement an explicit timeout to masters, and will
* be able to detect a link disconnection even if the TCP connection
* will not actually go down. */
if (!(server.cronloops % (server.repl_ping_slave_period * REDIS_HZ))) {
listIter li;
listNode *ln;
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
redisClient *slave = ln->value;
/* Don't ping slaves that are in the middle of a bulk transfer
* with the master for first synchronization. */
if (slave->replstate == REDIS_REPL_SEND_BULK) continue;
if (slave->replstate == REDIS_REPL_ONLINE) {
/* If the slave is online send a normal ping */
addReplySds(slave,sdsnew("*1\r\n$4\r\nPING\r\n"));
} else {
/* Otherwise we are in the pre-synchronization stage.
* Just a newline will do the work of refreshing the
* connection last interaction time, and at the same time
* we'll be sure that being a single char there are no
* short-write problems. */
if (write(slave->fd, "\n", 1) == -1) {
/* Don't worry, it's just a ping. */
}
}
}
}
}
int connectWithMaster(void) { //建立連接
int fd;
fd = anetTcpNonBlockConnect(NULL,server.masterhost,server.masterport);
if (fd == -1) {
redisLog(REDIS_WARNING,"Unable to connect to MASTER: %s",
strerror(errno));
return REDIS_ERR;
}
if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
AE_ERR)
{//綁定事件syncWithMaster,如果連接建立好則發送同步請求
close(fd);
redisLog(REDIS_WARNING,"Can't create readable event for SYNC");
return REDIS_ERR;
}
server.repl_transfer_lastio = server.unixtime;
server.repl_transfer_s = fd;
server.repl_state = REDIS_REPL_CONNECTING;
return REDIS_OK;
}
(2)主服務器在收到同步請求後執行syncCommand函數產生子進程進行rdb快照文件的生成。syncCommand函數位於replication.c文件
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.repl_state != 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. */ // 有回覆等待時,不進行 SYNC 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 */ // 檢查是否已經有 BGSAVE 在執行,否則就創建一個新的 BGSAVE 任務 if (server.rdb_child_pid != -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 */ // 已有 BGSAVE 在執行,檢查它能否用於當前客戶端的 SYNC 操作 redisClient *slave; listNode *ln; listIter li; // 檢查是否有其他客戶端在等待 SYNC 進行 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. */ // 找到一個同樣在等到 SYNC 的客戶端 // 設置當前客戶端的狀態,並複製 buffer 。 copyClientOutputBuffer(c,slave); 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 */ // 沒有客戶端在等待 SYNC ,當前客戶端只能等待下次 BGSAVE 進行 c->replstate = REDIS_REPL_WAIT_BGSAVE_START; redisLog(REDIS_NOTICE,"Waiting for next BGSAVE for SYNC"); } } else { // 沒有 BGSAVE 在進行,自己啓動一個。 /* Ok we don't have a BGSAVE in progress, let's start one */ redisLog(REDIS_NOTICE,"Starting BGSAVE for SYNC"); if (rdbSaveBackground(server.rdb_filename) != REDIS_OK) { redisLog(REDIS_NOTICE,"Replication failed, can't BGSAVE"); addReplyError(c,"Unable to perform background save"); return; } // 等待 BGSAVE 結束 c->replstate = REDIS_REPL_WAIT_BGSAVE_END; } c->repldbfd = -1; c->flags |= REDIS_SLAVE; c->slaveseldb = 0; listAddNodeTail(server.slaves,c); return; }
(3)在以後的時間事件函數中會檢查快照生成子進程是否結束,若結束更新服務器的狀態,向所有slave服務器發送快照文件。
/* Check if a background saving or AOF rewrite in progress terminated. */
// 如果 BGSAVE 或者 BGREWRITEAOF 正在進行
// 那麼檢查它們是否已經執行完畢
if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
int statloc;
pid_t pid;
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
int exitcode = WEXITSTATUS(statloc);
int bysignal = 0;
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
if (pid == server.rdb_child_pid) { //如果bgsave操作結束更改服務器狀態,根據子進程的響應值進行下一步操作
backgroundSaveDoneHandler(exitcode,bysignal);
} else if (pid == server.aof_child_pid) {
backgroundRewriteDoneHandler(exitcode,bysignal);
} else {
redisLog(REDIS_WARNING,
"Warning, detected child with unmatched pid: %ld",
(long)pid);
}
// 如果 BGSAVE 和 BGREWRITEAOF 都已經完成,那麼重新開始 REHASH
updateDictResizePolicy();
}
}
/* A background saving child (BGSAVE) terminated its work. Handle this. */
/*
* 根據 BGSAVE 子進程的返回值,對服務器狀態進行更新
*/
void backgroundSaveDoneHandler(int exitcode, int bysignal) {
// 保存成功
if (!bysignal && exitcode == 0) {
redisLog(REDIS_NOTICE,
"Background saving terminated with success");
server.dirty = server.dirty - server.dirty_before_bgsave;
server.lastsave = time(NULL);
server.lastbgsave_status = REDIS_OK;
// 保存失敗
} else if (!bysignal && exitcode != 0) {
redisLog(REDIS_WARNING, "Background saving error");
server.lastbgsave_status = REDIS_ERR;
// 子進程被終結
} else {
redisLog(REDIS_WARNING,
"Background saving terminated by signal %d", bysignal);
rdbRemoveTempFile(server.rdb_child_pid);
server.lastbgsave_status = REDIS_ERR;
}
// 更新服務器狀態
server.rdb_child_pid = -1;
server.rdb_save_time_last = time(NULL)-server.rdb_save_time_start;
server.rdb_save_time_start = -1;
/* Possibly there are slaves waiting for a BGSAVE in order to be served
* (the first stage of SYNC is a bulk transfer of dump.rdb) */
// 將 rdb 文件保存完畢的消息報告可能正在等待複製的附屬節點
updateSlavesWaitingBgsave(exitcode == 0 ? REDIS_OK : REDIS_ERR);
}
void updateSlavesWaitingBgsave(int bgsaveerr) {
listNode *ln;
int startbgsave = 0;
listIter li;
// 遍歷所有附屬節點
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
redisClient *slave = ln->value;
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START) {
// 告訴那些這次不能同步的客戶端,可以等待下次 BGSAVE 了。
startbgsave = 1;
slave->replstate = REDIS_REPL_WAIT_BGSAVE_END;
} else if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_END) {
// 這些是本次可以同步的客戶端
struct redis_stat buf;
// 如果 BGSAVE 失敗,釋放 slave 節點
if (bgsaveerr != REDIS_OK) {
freeClient(slave);
redisLog(REDIS_WARNING,"SYNC failed. BGSAVE child returned an error");
continue;
}
// 打開 .rdb 文件
if ((slave->repldbfd = open(server.rdb_filename,O_RDONLY)) == -1 ||
// 如果打開失敗,釋放並清除
redis_fstat(slave->repldbfd,&buf) == -1) {
freeClient(slave);
redisLog(REDIS_WARNING,"SYNC failed. Can't open/stat DB after BGSAVE: %s", strerror(errno));
continue;
}
// 偏移量
slave->repldboff = 0;
// 數據庫大小(.rdb 文件的大小)
slave->repldbsize = buf.st_size;
// 狀態
slave->replstate = REDIS_REPL_SEND_BULK;
// 清除 slave->fd 的寫事件
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
// 創建一個將 .rdb 文件內容發送到附屬節點的寫事件
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) {
freeClient(slave);
continue;
}
}
}
// 有客戶端沒有在這次 BGSAVE 中成功同步
if (startbgsave) {
// 再啓動一次 BGSAVE
if (rdbSaveBackground(server.rdb_filename) != REDIS_OK) {
// 如果 BGSAVE 失敗,清空附屬節點
listIter li;
listRewind(server.slaves,&li);
redisLog(REDIS_WARNING,"SYNC failed. BGSAVE failed");
while((ln = listNext(&li))) {
redisClient *slave = ln->value;
if (slave->replstate == REDIS_REPL_WAIT_BGSAVE_START)
freeClient(slave);
}
}
}
}
/*
* 將主節點的 .rdb 文件內容發送到附屬節點
*
* 每次最大發送的字節數量有 REDIS_IOBUF_LEN 決定,
* 視乎文件的大小和服務器的狀態,整個發送過程可能會執行多次
*/
void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
redisClient *slave = privdata;
REDIS_NOTUSED(el);
REDIS_NOTUSED(mask);
char buf[REDIS_IOBUF_LEN];
ssize_t nwritten, buflen;
// 剛開始執行 .rdb 文件的發送?
if (slave->repldboff == 0) {
/* Write the bulk write count before to transfer the DB. In theory here
* we don't know how much room there is in the output buffer of the
* socket, but in pratice SO_SNDLOWAT (the minimum count for output
* operations) will never be smaller than the few bytes we need. */
sds bulkcount;
// 首先將主節點 .rdb 文件的大小發送到附屬節點
bulkcount = sdscatprintf(sdsempty(),"$%lld\r\n",(unsigned long long)
slave->repldbsize);
if (write(fd,bulkcount,sdslen(bulkcount)) != (signed)sdslen(bulkcount))
{
sdsfree(bulkcount);
freeClient(slave);
return;
}
sdsfree(bulkcount);
}
// 設置主節點 .rdb 文件的偏移量
lseek(slave->repldbfd,slave->repldboff,SEEK_SET);
// 讀取主節點 .rdb 文件的數據到 buf
buflen = read(slave->repldbfd,buf,REDIS_IOBUF_LEN);
if (buflen <= 0) {
// 主節點 .rdb 文件讀取錯誤,返回
redisLog(REDIS_WARNING,"Read error sending DB to slave: %s",
(buflen == 0) ? "premature EOF" : strerror(errno));
freeClient(slave);
return;
}
// 將 buf 發送給附屬節點
if ((nwritten = write(fd,buf,buflen)) == -1) {
// 附屬節點寫入出錯,返回
redisLog(REDIS_VERBOSE,"Write error sending DB to slave: %s",
strerror(errno));
freeClient(slave);
return;
}
// 更新偏移量
slave->repldboff += nwritten;
// .rdb 文件全部發送完畢
if (slave->repldboff == slave->repldbsize) {
// 關閉 .rdb 文件
close(slave->repldbfd);
// 重置
slave->repldbfd = -1;
// 刪除發送事件
aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
// 更新附屬節點狀態
slave->replstate = REDIS_REPL_ONLINE;
// TODO:
if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
sendReplyToClient, slave) == AE_ERR) {
freeClient(slave);
return;
}
redisLog(REDIS_NOTICE,"Synchronization with slave succeeded");
}
}
(4)執行命令的過程中如果有寫操作,也都會往slave服務器發送一份。
processCommand函數用於處理服務器收到的所有命令,其核心會調用call函數執行命令。
/* Call() is the core of Redis execution of a command */ /* * 執行客戶端指定的命令 */ void call(redisClient *c, int flags) { long long dirty, start = ustime(), duration; /* Sent the command to clients in MONITOR mode, only if the commands are * not geneated from reading an AOF. */ // 如果命令不是來自於 AOF 文件,並且命令可以發送給 MONITOR // 那麼將命令發送給 MONITOR if (listLength(server.monitors) && !server.loading && !(c->cmd->flags & REDIS_CMD_SKIP_MONITOR)) { replicationFeedMonitors(c,server.monitors,c->db->id,c->argv,c->argc); } /* Call the command. */ redisOpArrayInit(&server.also_propagate); dirty = server.dirty; // 執行命令 c->cmd->proc(c); // 計算命令造成多少個 key 變成 dirty dirty = server.dirty-dirty; // 計算執行命令耗費的時間 duration = ustime()-start; /* When EVAL is called loading the AOF we don't want commands called * from Lua to go into the slowlog or to populate statistics. */ // 命令由 AOF 文件在 lua 腳本中執行時 // 不開啓 slowlog 和 統計功能 if (server.loading && c->flags & REDIS_LUA_CLIENT) flags &= ~(REDIS_CALL_SLOWLOG | REDIS_CALL_STATS); /* Log the command into the Slow log if needed, and populate the * per-command statistics that we show in INFO commandstats. */ // 根據命令執行耗費的時間,看是否需要將命令添加到 slowlog if (flags & REDIS_CALL_SLOWLOG) slowlogPushEntryIfNeeded(c->argv,c->argc,duration); // 添加命令到統計數據 if (flags & REDIS_CALL_STATS) { c->cmd->microseconds += duration; c->cmd->calls++; } /* Propagate the command into the AOF and replication link */ // 傳播命令到 AOF 和附屬節點 if (flags & REDIS_CALL_PROPAGATE) { int flags = REDIS_PROPAGATE_NONE; if (c->cmd->flags & REDIS_CMD_FORCE_REPLICATION) flags |= REDIS_PROPAGATE_REPL; if (dirty) flags |= (REDIS_PROPAGATE_REPL | REDIS_PROPAGATE_AOF); if (flags != REDIS_PROPAGATE_NONE) propagate(c->cmd,c->db->id,c->argv,c->argc,flags); } /* Commands such as LPUSH or BRPOPLPUSH may propagate an additional * PUSH command. */ if (server.also_propagate.numops) { int j; redisOp *rop; for (j = 0; j < server.also_propagate.numops; j++) { rop = &server.also_propagate.ops[j]; propagate(rop->cmd, rop->dbid, rop->argv, rop->argc, rop->target); } redisOpArrayFree(&server.also_propagate); } server.stat_numcommands++; }void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags) {//傳播給定命令到 AOF 或附屬節點 if (server.aof_state != REDIS_AOF_OFF && flags & REDIS_PROPAGATE_AOF) feedAppendOnlyFile(cmd,dbid,argv,argc); if (flags & REDIS_PROPAGATE_REPL && listLength(server.slaves)) replicationFeedSlaves(server.slaves,dbid,argv,argc); }