深入分析Redis的主從復制機制


一、前言

  最近由於疫情影響,時間比較多,所以開始學習之前一直想學,但是卻沒時間學的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。主服務器在接收到用戶發來的寫指令時,不僅僅會將寫指令發送給從服務器進行同步,同時還會將這個指令放入到復制積壓緩沖區中,目的是在從服務器沒有成功接收到的時候能夠重傳。復制緩沖區的結構大致如下:

  可以看到,對於復制積壓緩沖區中的每一個字節,都有一個對應的偏移量。如果當前緩沖區已經滿了,但是又有新的指令需要放入其中,則會將最先放入其中的指令移除,騰出足夠空間后,將新指令放入(先入先出),所以,緩沖區中能夠存儲的指令是有限的。

(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的主從復制做了一個比較詳細的描述,時間太晚了,就不說別的了,希望能夠為需要的人答疑解惑吧。


四、參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM