原文連結:https://www.linkinstars.com/post/9ddfbd5e.html
前言
今天繼續來看看有關 Redis 的一個問題,主從復制。通常,對於大多數的場景來說,讀比寫更多,於是對於緩存的水平擴充套件,其中的一個方式 「主從復制」 就是一個常見的思路。有了主從復制,那麽可以擴充套件出很多從節點來應對大量的讀請求。那麽問題來了 Redis 的主從復制是如何實作的呢?
PS:本文僅關心復制的機制,不關心主節點下線重新選等等異常情況。
前置知識
你需要知道 Redis 的持久化方式,RDB 和 AOF
Redis 執行命令的基本思路
審題
題目本身不復雜,提問者問這個問題的想法可能會有下面幾個方面:
了解 Redis 的主從復制機制的話,如果在實際使用過程中出現問題就更容易排查。
在設計復制機制的時候需要註意和考慮什麽問題。
這樣的設計是否能套用在別的場景中。
嘗試思考
假設你完全沒有看過 Redis 源碼來思考這個問題,可以從下面幾個角度去嘗試分析,並猜測答案。
首先,想到一個關系戶,也就是我們常用的 Mysql,它也有主從復制,如果你了解 binlog 那麽可以嘗試從這裏著手,雖然不同,但思路應該是差不多的。
然後,簡化問題,主從復制,無非就是將數據發送過去,對方接受保存。
不可能每次都復制的是全量數據,那麽肯定需要有機制去確保如何每次復制增量的數據。
復制的是什麽?
復制的是數據本身?數據只要變動就將變動的 kv 直接扔給從節點?
復制的是執行命令?將客戶端執行的命令發送給子節點執行一次?
解決
有了上面的思考,其實實際也就有思路的。首先主從復制肯定有兩種情況,一種就是第一次復制,也就是要執行一次全量復制,將主節點的所有數據到復制到從節點上去;另一種就是增量復制,在數據同步之後後續的增量數據保持同步。
全量同步
持久化數據
因為需要全量同步所有數據,我們知道 Redis 數據在記憶體裏面,既然要發送,那勢必需要先持久化一次。也就是先 SYNC 一遍,透過方法
startBgsaveForReplication
來完成的。
程式碼位置在:https://github.com/redis/redis/blob/14f802b360ef52141c83d477ac626cc6622e4eda/src/replication.c#L855
這個問題不大, 就是保存一個 RDB 檔。
發送數據
這個也很不難,就是將數據直接扔過去就好了。
程式碼位置在:https://github.com/redis/redis/blob/14f802b360ef52141c83d477ac626cc6622e4eda/src/replication.c#L1402
增量同步
後續的任務就是增量同步後續產生的數據了。在猜測時我們想到有兩種復制方式,一種是直接復制數據,這種方式復制 RDB 是可行,在全量同步的時候用這個肯定更好,如果同步命令那麽從節點還需再執行一次過於復雜和麻煩,還耗時。而對於後續的增量同步來說,肯定是同步命令來的更高效(不過還是得看實際)。
下面就是傳播命令的方法:
這個方法就是將增量命令傳播給 AOF 和 Slaves,AOF 就是持久化的另一種方式,而 Slaves 就是我們需要同步的從節點了。具體
replicationFeedSlaves
方法就不具體看了。
監控狀態
這個其實是我們在猜測的時候漏掉的,想來也是,master 肯定需要知道 slave 的狀態,如果連不上了,肯定要處理,在 replication.c 中有這樣一個方法:
看名字和註釋就秒懂了,每秒執行一次的同步定時任務。
而其中呼叫了
replicationFeedSlaves
方法,也就是 PING 一下,看看活著沒:
可能導致的問題
第一次同步 RDB 時間太長?
如果我們 redis 存放的數據很多,第一次同步會有兩個時間,一個是 bgsave 的時間,這個時間其實還好,畢竟平時就是要執行的,而第二個時間就是傳輸數據的時間,這個時間就取決於頻寬了。
不過首先這個操作時,主節點依舊可以被讀寫,只不過操作均被緩存了,所以倒是不必擔心這段時間無法被使用。難就在如果數據過多可能真的會導致一個問題就是,同步->超時->重試,然後不斷迴圈,所以為了避免這樣的情況出現,建議 Redis 前往別直接把主機全部記憶體吃完。通常 maxmemory 設定為 75% 就相對不會出現問題,也不容易 OOM。
當然,有人肯定會問,能不能直接先手動拷貝 RDB 檔來減少同步時間,實際操作過我告訴你,不要手動操作,容易出現意想不到的問題,當出現問題之後,數據還是會不同步,還是會執行重新同步,還不如第一次就手動讓程式自己來。
最佳化
傳播 cache
命令在傳播的階段設定了主從同步發送的緩沖區,透過維護一個緩沖區來保證當主節點無需等待,從節點自己憑實力拿就好了,即使有一段時間突然抖動了一下,也沒事,緩沖區裏面還有,繼續同步就行嘞。但當完全超過緩沖區的承受範圍,那麽還是需要執行一次全量同步來保證數據一致。
無盤載入
之前看程式碼的時候就註意到了一個參數
repl_diskless_sync
轉譯過來就是無盤同步,顯然這個最佳化是 Redis 註意到第一次同步的時候,如果馬上寫入 RDB 顯然是有點慢了,直接 dump 記憶體肯定會來的更快,所以這就是無盤,也就是不先落盤。
總結
最後用一張圖來總結整個過程:
我們看著這個圖我們也可以想到,其實這樣復制的策略在絕大多數復制的場景中都是適用的,如果實際沒有命令這個說法,那就將數據拆分成小塊(chunk)來同步。需要註意點和最佳化點可能 Redis 都幫你想好了,對著抄就可以了。所以,我稱為一種設計為 」單向同步「,那麽如果什麽是多向同步呢?也就是多個人同時編輯或運算元據,互相同步的策略,此時就需要一些 diff 演算法和策略了,你也可以考慮設計看看,看具體會遇到什麽問題。
往期推薦
點亮,伺服器三年不宕機