杭州某小廠:Redis主從握手流程

印象很深刻,之前我去面試就被一位老面試官問了:Redis主從複製機制,今天咱們就來分析分析!

下面詳細分析Redis主從複製機制中主從握手的過程。

Redis主從複製機制中有兩個角色:主節點與從節點。

主節點處理用戶請求,並將數據複製給從節點。

主從複製機制主要有以下作用:

(1)數據冗餘,將數據熱備份到從節點,即使主節點由於磁盤損壞丟失數據,從節點依然保留數據副本。

(2)讀/寫分離,可以由主節點提供寫服務,從節點提供讀服務,提高Redis服務整體吞吐量。

(3)故障恢復,主節點故障下線後,可以手動將從節點切換爲主節點,繼續提供服務。

(4)高可用基礎,主從複製機制是Sentinel和Cluster機制的基礎,Sentinel和Cluster都實現了故障轉移,即主節點故障停止後,Redis負責選擇一個從節點切換爲主節點,繼續提供服務。

下面將主從複製流程分爲三個階段。

(1)握手階段:主從連接成功後,從節點需要將自身信息(如IP地址、端口等)發送給主節點,以便主節點能認識自己。

(2)同步階段:從節點連接主節點後,需要先同步數據,數據達到一致(或者只有最新的變更不一致)後才進入複製階段。

Redis支持兩種同步機制:

全量同步:從節點發送命令PSYNC ? -1,要求進行全量同步,主節點返回響應+FULLRESYNC,表明同意全量同步。隨後,主節點生成RDB數據併發送給從節點。這種方式常用於新的從節點首次同步數據。

部分同步:從節點發送命令PSYNC replid offset,要求進行部分同步,主節點響應+CONTINUE,表明同意部分同步。主節點只需要把複製積壓區中offset偏移量之後的命令發送給從節點即可(主節點會將執行的寫命令都寫入複製積壓區)。這種方式常用於主從連接斷開重連時同步數據。如果offset不在複製積壓區中,那麼主節點也會返回+FULLRESYNC,要求進行全量同步。

(3)複製階段:主節點在運行期間,將執行的寫命令傳播給從節點,從節點接收並執行這些命令,從而達到複製數據的效果。Redis使用的是異步複製,主節點傳播命令後,並不會等待從節點返回ACK確認。異步複製的優點是低延遲和高性能,缺點是可能在短期內主從節點數據不一致。

本文中指的命令,包含命令名及執行命令的參數。

PSYNC命令涉及以下屬性:

server.master_repl_offset:記錄當前服務器已執行命令的偏移量。

server.replid:40位十六進制的隨機字符串,在主節點中是自身ID,在從節點中記錄的是主節點ID。

server.replid2:用於主節點,存放上一個主節點ID。

server.repl_backlog:複製積壓區,主節點將最近執行的寫命令寫入複製積壓區,用於實現部分同步。

下面介紹一下Redis主從握手流程。

主從複製的機制是由從節點發起流程,我們可以發送REPLICAOF命令到某個服務器,要求它成爲指定服務器的從節點:

REPLICAOF <masterip> <masterport>
或者在配置文件中添加配置REPLICAOF <masterip> <masterport>,這樣Redis服務器啓動後將成爲指定服務器的從節點。

提示:從Redis 5開始爲SLAVEOF命令提供別名REPLICAOF,這兩個命令的作用一樣。

下面以從節點的視角,分析主從握手的過程。

從節點握手階段涉及以下屬性。

server.repl_state:用於從節點,標誌從節點當前複製狀態。有如下值:

REPL_STATE_NONE:無主從複製關係。

REPL_STATE_CONNECT:待連接。

REPL_STATE_CONNECTING:正在連接。

…(部分握手狀態並沒有列出)

REPL_STATE_TRANSFER:從節點正在接收RDB數據。

REPL_STATE_CONNECTED:已連接,主從同步完成。

從節點使用replicaofCommand函數處理REPLICAOF命令。

該函數執行如下邏輯:

(1)如果處理的命令是REPLICAOF NO ONE,則將當前服務器轉換爲主節點,取消原來的主從複製關係,退出函數。

(2)調用replicationSetMaster函數,與給定服務器建立主從複製關係。

另外,我們在配置文件中配置REPLICAOF <masterip> <masterport>,Redis加載該配置,也會將server.repl_state設置爲REPL_STATE_CONNECT狀態(config.c)。

從節點server.repl_state進入REPL_STATE_CONNECT狀態後,主從複製流程已經開始。

serverCron時間事件負責對REPL_STATE_CONNECT狀態進行處理:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ...
    if (server.repl_state == REPL_STATE_CONNECT) {
        if (connectWithMaster() == C_OK) {
            serverLog(LL_NOTICE,"MASTER <-> REPLICA sync started");
        }
    }
}

調用connectWithMaster函數進行處理,該函數負責建立主從網絡連接:

int connectWithMaster(void) {
    // [1]
    server.repl_transfer_s = server.tls_replication ? connCreateTLS() : connCreateSocket();
    // [2]
    if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
                NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
        ...
        return C_ERR;
    }


    // [3]
    server.repl_transfer_lastio = server.unixtime;
    server.repl_state = REPL_STATE_CONNECTING;
    return C_OK;
}

【1】創建一個Socket套接字。connCreateTLS函數創建TLS連接,connCreateSocket函數創建TCP連接,它們都返回套接字文件描述符。該連接是主從節點網絡通信的連接,本書稱之爲主從連接。

【2】connConnect函數負責連接到主節點,並且在連接成功後調用syncWithMaster函數。

【3】從節點server.repl_state進入REPL_STATE_CONNECTING狀態。

網絡連接成功後,從節點調用syncWithMaster函數,進入握手階段:

void syncWithMaster(connection *conn) {
    char tmpfile[256], *err = NULL;
    int dfd = -1, maxtries = 5;
    int psync_result;
    ...
    // [1]
    if (server.repl_state == REPL_STATE_CONNECTING) {
        connSetReadHandler(conn, syncWithMaster);
        connSetWriteHandler(conn, NULL);
        server.repl_state = REPL_STATE_RECEIVE_PONG;
        err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);
        if (err) goto write_error;
        return;
    }
    ...
    // [2]
    if (server.repl_state != REPL_STATE_RECEIVE_PSYNC) {
        goto error;
    }


    // more
}

【1】根據server.repl_state狀態,執行對應操作。

從節點發送給主節點的信息,主節點會記錄在從節點客戶端,並在INFO命令中輸出這些信息。另外,Sentinel模塊需要從主節點INFO命令響應中獲取這些從節點信息。

【2】執行到這裏,主從握手階段已經完成。server.repl_state必須處於REPL_STATE_ RECEIVE_PSYNC狀態,否則報錯。

下面使用Linux tcpdump工具抓取主從連接報文,分析主從節點握手階段的通信內容(主節點端口爲6000):

tcpdump tcp -i lo -nn port 6000 -T RESP
tcpdump支持RESP協議,最後一個選項-T RESP要求tcpdump以RESP協議格式解析報文。

其中6000端口爲主節點端口,60374端口爲從節點通信端口。從tcpdump的輸出可以清晰地看到主從節點在握手階段的通信內容。

提示:tcpdump解析後的RESP內容並不會展示數據類型的標誌符,如主節點對從節點PING命令的響應實際上是“-NOAUTH Authentication required.”,請讀者閱讀源碼時注意。

以主節點視角分析握手階段,主節點不斷處理來自從節點的命令(包括PING、AUTH、REPLCONF),感興趣的讀者可自行閱讀代碼。

好了,Redis主從握手流程到此就分析完畢了,這也算是看過源碼了,哈哈。

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