12.24 面試必問:Redis 是如何進行主從複製的?

我們知道,當有多臺 Redis 服務器時,肯定就有一臺主服務器和多臺從服務器。一般來說,主服務器進行寫操作,從服務器進行讀操作。


那麼,從服務器如何和主服務器進行數據同步的呢?

其實就是通過主從複製來實現的。

本篇內容主要分享 Redis 複製原理,以及複製分析等內容。

Redis 複製原理

為了避免單點故障,數據存儲需要進行多副本構建。同時由於 Redis 的核心操作是單線程模型的,單個 Redis 實例能處理的請求 TPS 有限。因此 Redis 自面世起,基本就提供了複製功能,而且對複製策略不斷進行優化。

面試必問:Redis 是如何進行主從複製的?

通過數據複製,Redis 的一個 master 可以掛載多個 slave,而 slave 下還可以掛載多個 slave,形成多層嵌套結構。所有寫操作都在 master 實例中進行,master 執行完畢後,將寫指令分發給掛在自己下面的 slave 節點。slave 節點下如果有嵌套的 slave,會將收到的寫指令進一步分發給掛在自己下面的 slave。

通過多個 slave,Redis 的節點數據就可以實現多副本保存,任何一個節點異常都不會導致數據丟失,同時多 slave 可以 N 倍提升讀性能。master 只寫不讀,這樣整個 master-slave 組合,讀寫能力都可以得到大幅提升。

master 在分發寫請求時,同時會將寫指令複製一份存入複製積壓緩衝,這樣當 slave 短時間斷開重連時,只要 slave 的複製位置點仍然在複製積壓緩衝,則可以從之前的複製位置點之後繼續進行復制,提升複製效率。

面試必問:Redis 是如何進行主從複製的?

主庫 master 和從庫 slave 之間通過複製 id 進行匹配,避免 slave 掛到錯誤的 master。Redis 的複製分為全量同步和增量同步。

Redis 在進行全量同步時,master 會將內存數據通過 bgsave 落地到 rdb,同時,將構建 內存快照期間 的寫指令,存放到複製緩衝中,當 rdb 快照構建完畢後,master 將 rdb 和複製緩衝隊列中的數據全部發送給 slave,slave 完全重新創建一份數據。

這個過程,對 master 的性能損耗較大,slave 構建數據的時間也比較長,而且傳遞 rdb 時還會佔用大量帶寬,對整個系統的性能和資源的訪問影響都比較大。

而增量複製,master 只發送 slave 上次複製位置之後的寫指令,不用構建 rdb,而且傳輸內容非常有限,對 master、slave 的負荷影響很小,對帶寬的影響可以忽略,整個系統受影響非常小。

在 Redis 2.8 之前,Redis 基本只支持全量複製。在 slave 與 master 斷開連接,或 slave 重啟後,都需要進行全量複製。在 2.8 版本之後,Redis 引入 psync,增加了一個複製積壓緩衝,在將寫指令同步給 slave 時,會同時在複製積壓緩衝中也寫一份。

在 slave 短時斷開重連後,上報master runid 及複製偏移量。如果 runid 與 master 一致,且偏移量仍然在 master 的複製緩衝積壓中,則 master 進行增量同步。

但如果 slave 重啟後,master runid 會丟失,或者切換 master 後,runid 會變化,仍然需要全量同步。

因此 Redis 自 4.0 強化了 psync,引入了 psync2。在 pysnc2 中,主從複製不再使用 runid,而使用 replid(即複製id) 來作為複製判斷依據。同時 Redis 實例在構建 rdb 時,會將 replid 作為 aux 輔助信息存入 rbd。重啟時,加載 rdb 時即可得到 master 的複製 id。從而在 slave 重啟後仍然可以增量同步。

在 psync2 中,Redis 每個實例除了會有一個複製 id 即 replid 外,還有一個 replid2。Redis 啟動後,會創建一個長度為 40 的隨機字符串,作為 replid 的初值,在建立主從連接後,會用 master的 replid 替換自己的 replid。同時會用 replid2 存儲上次 master 主庫的 replid。這樣切主時,即便 slave 彙報的複製 id 與新 master 的 replid 不同,但和新 master 的 replid2 相同,同時複製偏移仍然在複製積壓緩衝區內,仍然可以實現增量複製。

Redis 複製分析

在設置 master、slave 時,首先通過配置或者命令 slaveof no one 將節點設置為主庫。然後其他各個從庫節點,通過 slaveof $master_ip $master_port,將其他從庫掛在到 master 上。

同樣方法,還可以將 slave 節點掛載到已有的 slave 節點上。在準備開始數據複製時,slave 首先會主動與 master 創建連接,並上報信息。具體流程如下。

面試必問:Redis 是如何進行主從複製的?

slave 創建與 master 的連接後,首先發送 ping 指令,如果 master 沒有返回異常,而是返回 pong,則說明 master 可用。

如果 Redis 設置了密碼,slave 會發送 auth $masterauth 指令,進行鑑權。當鑑權完畢,從庫就通過 replconf 發送自己的端口及 IP 給 master。

接下來,slave 繼續通過 replconf 發送 capa eof capa psync2 進行復製版本校驗。如果 master 校驗成功。從庫接下來就通過 psync 將自己的複製 id、複製偏移發送給 master,正式開始準備數據同步。

主庫接收到從庫發來的 psync 指令後,則開始判斷可以進行數據同步的方式。

前面講到,Redis 當前保存了複製 id,replid 和 replid2。如果從庫發來的複製 id,與 master 的複製 id(即 replid 和 replid2)相同,並且複製偏移在複製緩衝積壓中,則可以進行增量同步。master 發送 continue 響應,並返回 master 的 replid。slave 將 master 的 replid 替換為自己的 replid,並將之前的複製 id 設置為 replid2。之後,master 則可繼續發送,複製偏移位置 之後的指令,給 slave,完成數據同步。

如果主庫發現從庫傳來的複製 id 和自己的 replid、replid2 都不同,或者複製偏移不在複製積壓緩衝中,則判定需要進行全量複製。master 發送 fullresync 響應,附帶 replid 及複製偏移。然後, master 根據需要構建 rdb,並將 rdb 及複製緩衝發送給 slave。

對於增量複製,slave 接下來就等待接受 master 傳來的複製緩衝及新增的寫指令,進行數據同步。

而對於全量同步,slave 會首先進行,嵌套複製的清理工作,比如 slave 當前還有嵌套的 子slave,則該 slave 會關閉嵌套 子slave 的所有連接,並清理自己的複製積壓緩衝。

然後,slave 會構建臨時 rdb 文件,並從 master 連接中讀取 rdb 的實際數據,寫入 rdb 中。在寫 rdb 文件時,每寫 8M,就會做一個 fsync操作, 刷新文件緩衝。當接受 rdb 完畢則將 rdb 臨時文件改名為 rdb 的真正名字。

接下來,slave 會首先清空老數據,即刪除本地所有 DB 中的數據,並暫時停止從 master 繼續接受數據。然後,slave 就開始全力加載 rdb 恢復數據,將數據從 rdb 加載到內存。

在 rdb 加載完畢後,slave 重新利用與 master 的連接 socket,創建與 master 連接的 client,並在此註冊讀事件,可以開始接受 master 的寫指令了。

此時,slave 還會將 master 的 replid 和複製偏移設為自己的複製 id 和複製偏移 offset,並將自己的 replid2 清空,因為,slave 的所有嵌套 子slave 接下來也需要進行全量複製。

最後,slave 就會打開 aof 文件,在接受 master 的寫指令後,執行完畢並寫入到自己的 aof 中。

相比之前的 sync,psync2 優化很明顯。在短時間斷開連接、slave 重啟、切主等多種場景,只要延遲不太久,複製偏移仍然在複製積壓緩衝,均可進行增量同步。

master 不用構建併發送巨大的 rdb,可以大大減輕 master 的負荷和網絡帶寬的開銷。同時,slave 可以通過輕量的增量複製,實現數據同步,快速恢復服務,減少系統抖動。

但是,psync 依然嚴重依賴於複製緩衝積壓,太大會佔用過多內存,太小會導致頻繁的全量複製。而且,由於內存限制,即便設置相對較大的複製緩衝區,在 slave 斷開連接較久時,仍然很容易被複制緩衝積壓沖刷,從而導致全量複製。


分享到:


相關文章: