深入分析Redis的主從複製機制

特務依昂發表於2020-04-26

一、前言

  最近由於疫情影響,時間比較多,所以開始學習之前一直想學,但是卻沒時間學的Redis。這兩天研究了一下Redis的持久化以及主從複製機制,現在已經很晚了,就不多廢話了。這篇部落格就來談一談Redis的主從複製機制。在這裡需要提醒一下,主從複製依賴於Redis的快照持久化(RDB),所以如果不瞭解持久化,請先去研究那一塊的內容,可以看看這篇部落格:詳細分析Redis的持久化操作—RDB與AOF


二、正文

2.1 什麼是主從複製

  首先我們來談一談最基本的問題——什麼是主從複製,為什麼需要它?我們知道,現在的應用基本上都會使用叢集進行部署,同一個應用部署在多臺伺服器上,各臺伺服器互相同步,各自承擔一部分任務,以此來減輕單臺伺服器的壓力。而主從複製就是Redis用來對儲存相同資料的多臺伺服器進行同步的機制。

  假設我們只在一臺伺服器上部署了Redis伺服器,那所有需要訪問Redis伺服器的請求,都需要這一臺伺服器來處理,這對伺服器來說有很大的壓力。如果訪問的很頻繁,那麼一臺伺服器根本處理不過來,所以我們需要多臺伺服器同時部署Redis,然後每一臺伺服器承擔一部分任務。

  如果我們部署了多臺Redis伺服器,儲存相同的資料,為同一個應用進行服務,那麼不難想到,我們需要處理一個問題——資料同步。我們必須保證這多臺伺服器的Redis資料庫中,儲存的資料是一致的,而且都應該是正確的資料,否則將會導致對請求進行錯誤的處理,比如查詢出的是過期的資料,或者對已經過期的資料進行了修改。而主從複製,就是Redis對這多臺伺服器進行資料同步的機制。

  在主從複製機制中,Redis將伺服器分為主伺服器從伺服器,主伺服器負責接收使用者提交的修改指令,修改資料庫中的資料,同時將修改同步到從伺服器中,而從伺服器的任務就是與主伺服器進行資料同步,並分擔本應該由主伺服器執行的查詢請求,減小主伺服器的壓力,除此之外,為了減輕主伺服器的壓力,我們也可以關閉主伺服器的持久化操作,而讓從伺服器來進行持久化。


2.2 主從複製的實現過程

  完整的主從複製包含以下兩步:

  1. 同步:將從伺服器當前的狀態,更新為主伺服器當前的狀態,也就是使用主伺服器中儲存的資料,替換掉從伺服器的資料;
  2. 命令傳播:主伺服器執行每一次修改操作後,都需要告知從伺服器,讓從伺服器執行相同的操作,以保證一致性;

  下面我就來分別分析一下這兩個過程的詳細實現。


2.3 同步的實現原理

  從伺服器與主伺服器同步,需要使用到SYNC指令,詳細的執行流程如下:

  1. 從伺服器連線到主伺服器,並向主伺服器傳送SYNC指令;
  2. 主伺服器接收到SYNC指令後,開始執行BGSAVE指令(快照持久化),此時主伺服器將呼叫fock(),建立一個子程式,子程式去生成Redis當前狀態的一個快照;在這個過程中,新到達主伺服器的寫指令將會被記錄在緩衝區;
  3. 主伺服器執行完BGSAVE後,將快照檔案傳送給從伺服器,在傳送的過程中,如果還有新的寫指令到達,也會繼續記錄在緩衝區;從伺服器接收到主伺服器發來的快照檔案後,將丟棄自己記憶體中的資料,開始載入快照檔案中記錄的資料,載入完成後,就可以處理接收到的請求了;
  4. 主伺服器在傳送完快照檔案後,開始將緩衝區中記錄的寫指令也同步到從伺服器;從伺服器接收到主伺服器發來的指令,便依次執行這些指令,執行完後,就與主伺服器的狀態一致了;

2.4 命令傳播的實現原理

  為什麼需要命令傳播?這個應該很好理解。經過上面的同步後,主伺服器與從伺服器儲存的資料就一致了,這之後,從伺服器就可以分擔查詢操作,但是寫操作還是需要主伺服器完成。所以,雖然當前主從伺服器已經一致,但是主伺服器如果執行了一次寫操作,而從伺服器沒有執行,它們又將變成不一致的狀態。而命令傳播的實現原理很簡單:主伺服器每次執行寫操作,都會將這個寫指令傳送給從伺服器,從伺服器接收到後,也執行這個寫指令,這樣就能讓主伺服器和從伺服器持續的保持一致

  有人可能會想,為什麼是將指令傳送到從伺服器,而不是重新執行一次同步操作呢?答案很簡單,因為上面的同步操作,需要很大的開銷。執行BGSAVE指令建立快照,需要建立一個子程式,同時生成一個檔案,需要進行大量的磁碟IO,在資料量很大的情況下,可能會使主伺服器產生數毫秒甚至是一秒的停頓。而向從伺服器傳輸一個指令的開銷,要比上面的同步小得多。


2.5 部分重同步介紹

  以上介紹的主從複製過程,是一個開銷非常大,而且比較耗時的操作(主要是同步過程耗時),於是從Redis2.8開始,提供了一種優化機制——部分重同步。我們考慮這樣一種情況,假設一臺從伺服器已經與主伺服器完成了同步,進入了命令傳播階段,但是由於某些原因,主從伺服器之間的網路連線斷開了,從伺服器在一段時間後,重新連線上了主伺服器。按理來說,從伺服器和主伺服器斷開連線的這段時間,沒有同步對主伺服器的寫操作,此時它們已經不一致了,那麼從伺服器需要重新執行一次主從複製,這又是一次非常耗時的操作。而Redis2.8之後,提供了一種優化機制,若在上面的情況發生時,如果滿足某些條件(具體條件之後敘述),可以不進行一次完整的主從複製,而是隻同步斷開連線的這段時間裡,沒有同步的操作,這就是部分重同步。

  Redis2.8之後,提供了一個新的指令來實現部分重同步,這個指令就是PSYNC。從2.8開始,實現主從複製使用的就不是SYNC了,而是PSYNC,它可以算是SYNC的升級版本。PSYNC支援兩種模式:

  • 完整重同步:如果Redis判斷當前從伺服器需要與主伺服器重新進行一次完整的主從複製,則PSYNC指令將執行與SYNC指令完全一樣的操作,上面已經描述過了,這裡就不重複敘述了;
  • 部分重同步:若從伺服器與主伺服器斷線重連後,滿足某些條件,則不進行完整重同步,而是隻同步斷線過程中,沒有同步的部分;

2.6 部分重同步的實現原理

  下面我們就來詳細分析一下,部分重同步是如何實現的。部分重同步需要依賴以下三個部分:

  • 伺服器的執行id
  • 主伺服器的複製積壓緩衝區;
  • 主從伺服器的複製偏移量;

(1)伺服器的執行 id

  每一臺伺服器都會被分配一個執行id,用來標識伺服器的身份。從伺服器在與一臺主伺服器連線後,會記錄主伺服器的id。從伺服器與主伺服器斷開後,可能會重新連線一臺主伺服器,但是並不一定就是原來的那一臺。當從伺服器連線到一臺主伺服器後,會向主伺服器傳送自己記錄的主伺服器id,主伺服器判斷這是不是自己,如果是,表明從伺服器之前連線的就是自己,則有可能可以使用部分重同步機制,否則,將重新進行一次完整同步。

(2)主伺服器的複製積壓緩衝區

  首先,複製積壓緩衝區是一個固定長度,先進先出的佇列,預設 1MB。主伺服器在接收到使用者發來的寫指令時,不僅僅會將寫指令傳送給從伺服器進行同步,同時還會將這個指令放入到複製積壓緩衝區中,目的是在從伺服器沒有成功接收到的時候能夠重傳。複製緩衝區的結構大致如下:

  可以看到,對於複製積壓緩衝區中的每一個位元組,都有一個對應的偏移量。如果當前緩衝區已經滿了,但是又有新的指令需要放入其中,則會將最先放入其中的指令移除,騰出足夠空間後,將新指令放入,也就是LRU演算法(最近最久未使用),所以,緩衝區中能夠儲存的指令是有限的。

(3)主從伺服器的複製偏移量

  主伺服器和從伺服器會分別維護自己的複製偏移量,主伺服器每傳送出一個位元組,主伺服器偏移量就+1,而從伺服器每完成一個位元組的同步,從伺服器偏移量就+1

  什麼情況下會觸發部分重同步呢?答案就是:若從伺服器與主伺服器斷開連線,並重新連線到同一個主伺服器後,會將自己記錄的複製偏移量傳送給主伺服器,主伺服器判斷這個偏移量之後的所有位元組,是否還在複製緩衝區中,如果在,則表明可以進行部分重同步,將複製緩衝區中,這個偏移量之後的所有位元組傳送給從伺服器;若不完全包含,則表明從伺服器需要同步的資料,有一部分無法在緩衝區中找到,此時就需要進行一次完整同步。


2.7 配置從伺服器

  下面講一講如何將一臺Redis伺服器,配置為從伺服器,有兩種方式:

(1)配置檔案

  可以在配置Redis的配置檔案中,加入以下配置項:

slaveof 主伺服器ip 主伺服器埠

  在配置檔案中配置了上面這一行,則當前伺服器就是一臺從伺服器,它啟動時,就會嘗試區連線上面上面這個配置項指定好的主伺服器,並在連線成功後傳送PSYNC指令,完成之前介紹的步驟。

(2)指令

  第二種方式就是使用指令,在Redis伺服器輸入下面這一行指令,當前伺服器就會作為一個從伺服器,嘗試連線主伺服器,並進行主從複製:

127.0.0.1:6379> SLAVEOF 主伺服器ip 主伺服器埠

2.8 主從複製的安全性

  在使用Redis 複製功能時的設定中,強烈建議在 主伺服器 和 從伺服器 中啟用持久化。當不可能啟用時,例如由於非常慢的磁碟效能而導致的延遲問題,應該配置例項來避免重置後自動重啟

  為了更好地理解為什麼關閉了持久化並配置了自動重啟的 主伺服器 是危險的,檢查以下故障模式,這些故障模式中資料會從 主伺服器 和所有 從伺服器 中被刪除:

  1. 我們設定節點 A 為 主伺服器 並關閉它的持久化設定,節點 BC 從 節點 A 複製資料。
  2. 節點 A 崩潰,但是他有一些自動重啟的系統可以重啟程式。但是由於持久化被關閉了,節點重啟後其資料集合為空。
  3. 節點 B 和 節點 C會從節點 A 複製資料,但是節點 A 的資料集是空的,因此複製的結果是它們會銷燬自身之前的資料副本。

  當 Redis Sentinel 被用於高可用並且 主伺服器 關閉持久化,這時如果允許自動重啟程式也是很危險的。例如, 主伺服器 可以重啟的足夠快以致於 Sentinel 沒有探測到故障,因此上述的故障模式也會發生。任何時候資料安全性都是很重要的,所以如果 主伺服器 使用複製功能的同時未配置持久化,那麼自動重啟程式這項應該被禁用


三、總結

  以上就對Redis的主從複製做了一個比較詳細的描述,時間太晚了,就不說別的了,希望能夠為需要的人答疑解惑吧。


四、參考

相關文章