1. 程式人生 > >深入分析Redis的主從複製機制

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

# 一、前言   最近由於疫情影響,時間比較多,所以開始學習之前一直想學,但是卻沒時間學的``Redis``。這兩天研究了一下``Redis``的持久化以及主從複製機制,現在已經很晚了,就不多廢話了。這篇部落格就來談一談``Redis``的主從複製機制。在這裡需要提醒一下,主從複製依賴於``Redis``的快照持久化(``RDB``),所以如果不瞭解持久化,請先去研究那一塊的內容,可以看看這篇部落格:[詳細分析Redis的持久化操作—RDB與AOF](https://www.cnblogs.com/tuyang1129/p/12776526.html)。
# 二、正文 ## 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``。主伺服器在接收到使用者發來的寫指令時,不僅僅會將寫指令傳送給從伺服器進行同步,同時還會將這個指令放入到複製積壓緩衝區中,目的是在從伺服器沒有成功接收到的時候能夠重傳。複製緩衝區的結構大致如下: ![](https://img2020.cnblogs.com/blog/1324014/202004/1324014-20200426185726982-1958122731.png)   可以看到,對於複製積壓緩衝區中的每一個位元組,都有一個對應的偏移量。如果當前緩衝區已經滿了,但是又有新的指令需要放入其中,則會將最先放入其中的指令移除,騰出足夠空間後,將新指令放入,也就是``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`` 為 主伺服器 並關閉它的持久化設定,節點 ``B`` 和 ``C`` 從 節點 ``A`` 複製資料。 2. 節點 ``A`` 崩潰,但是他有一些自動重啟的系統可以重啟程序。但是由於持久化被關閉了,節點重啟後其資料集合為空。 3. 節點 ``B`` 和 節點 ``C``會從節點 ``A`` 複製資料,但是節點 ``A`` 的資料集是空的,因此複製的結果是它們會銷燬自身之前的資料副本。   當 ``Redis Sentinel`` 被用於高可用並且 主伺服器 關閉持久化,這時如果允許自動重啟程序也是很危險的。例如, 主伺服器 可以重啟的足夠快以致於 ``Sentinel`` 沒有探測到故障,因此上述的故障模式也會發生。**任何時候資料安全性都是很重要的,所以如果 主伺服器 使用複製功能的同時未配置持久化,那麼自動重啟程序這項應該被禁用**。
# 三、總結   以上就對``Redis``的主從複製做了一個比較詳細的描述,時間太晚了,就不說別的了,希望能夠為需要的人答疑解惑吧。
# 四、參考 - 《Redis實戰》 - [Redis官方文件—複製](http://www.redis.cn/topics/replication.html) -