源碼分析:redis主從複製

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);
}








發佈了86 篇原創文章 · 獲贊 23 · 訪問量 36萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章