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 支持兩種同步機制:
-
全量同步:從節點發送命令 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 ,這樣 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 ,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