Redis 主從握手流程,你真的瞭解了嗎?

Redis 是開源的 key-value 存儲系統,可作爲數據庫、緩存、消息組件。

Redis 的作者是 Salvatore Sanfilippo(網名爲 antirez),他在 2009 年開發完成並開源了 Redis。

Redis 由於性能極高、功能強大,迅速在業界流行,現已成爲高併發系統中最常用的組件之一。 

Redis 提供了多種類型的數據結構,如字符串(String)、散列(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set)等。

Redis 還是分佈式系統,主從集羣可以實現數據熱備份,哨兵(Sentinel)機制可以保證主從集羣高可用,Cluster 集羣則提供了水平擴展的能力。

Redis 還提供了持久化、Lua 腳本、Module 模塊、Stream 消息流、Tracking 機制等一系統強大功能,適用於各種業務場景。

Redis 是一個典型的 “小而美” 的程序。

Redis 實現簡單,源碼非常優雅簡潔,閱讀起來並不喫力,而且 Redis 功能齊全,涵蓋了數據存儲、分佈式、消息流等衆多特性,非常值得深入學習。

Redis 中的一個重要概念就是主從複製機制。

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

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

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

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

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

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

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

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

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

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

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

Redis 支持兩種同步機制:

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

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

PSYNC 命令涉及以下屬性:

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

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

REPLICAOF <masterip> <masterport>

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

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

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

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

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

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

該函數執行如下邏輯:

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

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

另外,我們在配置文件中配置 REPLICAOF ,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 主從握手流程到此就分析完畢了。

本內容摘自《Redis 核心原理與實踐》,想了解更多關於 Redis 的內容,歡迎閱讀此書。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/nbzETWfu42A-PZh6H-egNw