天天看點

Redis分布式基石——主從複制技術詳述

 本文已收錄于專欄

❤️《Redis精通系列》❤️

上千人點贊收藏,全套Redis學習資料,大廠必備技能!

目錄

1、簡介

2、主從複制的演進

2.1 版本2.8以前

2.1.1 同步

2.1.2 指令傳播

2.1.3 缺陷

2.2 版本2.8-4.0

2.2.1 改進點

2.2.2 psync如何實作

2.2.3 完整的psync

2.3 版本4.0

主從複制是Redis分布式的基石,也是Redis高可用的保障。在Redis中,被複制的伺服器稱為主伺服器(Master),對主伺服器進行複制的伺服器稱為從伺服器(Slave)。

Redis分布式基石——主從複制技術詳述

主從複制的配置非常簡單,有三種方式(其中IP-主伺服器IP位址/PORT-主伺服器Redis服務端口):

配置檔案——redis.conf檔案中,配置slaveof ip port

指令——進入Redis用戶端執行slaveof ip port

啟動參數—— ./redis-server --slaveof ip port

Redis的主從複制機制,并不是一開始就像6.x版本一樣完善,而是一個版本一個版本疊代而來的。它大體上經過三個版本的疊代:

2.8以前

2.8~4.0

4.0以後

随着版本的增長,Redis主從複制機制逐漸完善;但是他們的本質都是圍繞同步(sync)和指令傳播(command propagate)兩個操作展開:

同步(sync):指的是将從伺服器的資料狀态更新至主伺服器目前的資料狀态,主要發生在初始化或後續的全量同步。

指令傳播(command propagate):當主伺服器的資料狀态被修改(寫/删除等),主從之間的資料狀态不一緻時,主服務将發生資料改變的指令傳播給從伺服器,讓主從伺服器之間的狀态重回一緻。

2.8以前的版本,從伺服器對主伺服器的同步需要從伺服器向主伺服器發生sync指令來完成:

Redis分布式基石——主從複制技術詳述

從伺服器接收到用戶端發送的slaveof ip prot指令,從伺服器根據ip:port向主伺服器建立套接字連接配接

套接字成功連接配接到主伺服器後,從伺服器會為這個套接字連接配接關聯一個專門用于處理複制工作的檔案事件處理器,處理後續的主伺服器發送的RDB檔案和傳播的指令

開始進行複制,從伺服器向主伺服器發送sync指令

主伺服器接收到sync指令後,執行bgsave指令,主伺服器主程序fork的子程序會生成一個RDB檔案,同時将RDB快照産生後的所有寫操作記錄在緩沖區中

bgsave指令執行完成後,主伺服器将生成的RDB檔案發送給從伺服器,從伺服器接收到RDB檔案後,首先會清除本身的全部資料,然後載入RDB檔案,将自己的資料狀态更新成主伺服器的RDB檔案的資料狀态

主伺服器将緩沖區的寫指令發送給從伺服器,從伺服器接收指令,并執行。

主從複制同步步驟完成

當同步工作完成之後,主從之間需要通過指令傳播來維持資料狀态的一緻性。

如下圖,目前主從伺服器之間完成同步工作之後,主服務接收用戶端的DEL K6指令後删除了K6,此時從伺服器仍然存在K6,主從資料狀态并不一緻。為了維持主從伺服器狀态一緻,主伺服器會将導緻自己資料狀态發生改變的指令傳播到從伺服器執行,當從伺服器也執行了相同的指令之後,主從伺服器之間的資料狀态将會保持一緻。

Redis分布式基石——主從複制技術詳述

從上面看不出2.8以前版本的主從複制有什麼缺陷,這是因為我們還沒有考慮網絡波動的情況。了解分布式的兄弟們肯定聽說過CAP理論,CAP理論是分布式存儲系統的基石,在CAP理論中P(partition網絡分區)必然存在,Redis主從複制也不例外。當主從伺服器之間出現網絡故障,導緻一段時間内從伺服器與主伺服器之間無法通信,當從伺服器重新連接配接上主伺服器時,如果主伺服器在這段時間内資料狀态發生了改變,那麼主從伺服器之間将出現資料狀态不一緻。

在Redis 2.8以前的主從複制版本中,解決這種資料狀态不一緻的方式是通過重新發送sync指令來實作。雖然sync能保證主從伺服器資料狀态一緻,但是很明顯sync是一個非常消耗資源的操作。

sync指令執行,主從伺服器需要占用的資源:

主伺服器執行BGSAVE生成RDB檔案,會占用大量CPU、磁盤I/O和記憶體資源

主伺服器将生成的RDB檔案發送給從伺服器,會占用大量網絡帶寬,

從伺服器接收RDB檔案并載入,會導緻從伺服器阻塞,無法提供服務

從上面三點可以看出,sync指令不僅會導緻主伺服器的響應能力下降,也會導緻從伺服器在此期間拒絕對外提供服務。

針對2.8以前的版本,Redis在2.8之後對從伺服器重連後的資料狀态同步進行了改進。改進的方向是減少全量同步(full resynchronizaztion)的發生,盡可能使用增量同步(partial resynchronization)。在2.8版本之後使用psync指令代替了sync指令來執行同步操作,psync指令同時具備全量同步和增量同步的功能:

全量同步與上一版本(sync)一緻

增量同步中對于斷線重連後的複制,會根據情況采取不同措施;如果條件允許,仍然隻發送從服務缺失的部分資料。

Redis為了實作從伺服器斷線重連後的增量同步,增加了三個輔助參數:

複制偏移量(replication offset)

積壓緩沖區(replication backlog)

伺服器運作id(run id)

2.2.2.1 複制偏移量

在主伺服器和從伺服器内都會維護一個複制偏移量

主伺服器向從服務發送資料,傳播N個位元組的資料,主服務的複制偏移量增加N

從伺服器接收主伺服器發送的資料,接收N個位元組的資料,從伺服器的複制偏移量增加N

正常同步的情況如下:

Redis分布式基石——主從複制技術詳述

很明顯有了複制偏移量之後,從伺服器C斷線重連後,主伺服器隻需要發送從伺服器缺少的100位元組資料即可。但是主伺服器又是如何知道從伺服器缺少的是那些資料呢?

2.2.2.2 複制積壓緩沖區

複制積壓緩沖區是一個固定長度的隊列,預設為1MB大小。當主伺服器資料狀态發生改變,主伺服器将資料同步給從伺服器的同時會另存一份到複制積壓緩沖區中。

Redis分布式基石——主從複制技術詳述

當從伺服器斷線重連後,從伺服器通過psync指令将自己的複制偏移量(offset)發送給主伺服器,主伺服器便可通過這個偏移量來判斷進行增量傳播還是全量同步。

如果偏移量offset+1的資料仍然在複制積壓緩沖區中,那麼進行增量同步操作

反之進行全量同步操作,與sync一緻

Redis的複制積壓緩沖區的大小預設為1MB,如果需要自定義應該如何設定呢?

很明顯,我們希望能盡可能的使用增量同步,但是又不希望緩沖區占用過多的記憶體空間。那麼我們可以通過預估Redis從服務斷線後重連的時間T,Redis主伺服器每秒接收的寫指令的記憶體大小M,來設定複制積壓緩沖區的大小S。

S = 2 * M * T

注意這裡擴大2倍是為了留有一定的餘地,保證絕大部分的斷線重連都能采用增量同步。

2.2.2.3 伺服器運作 ID

看到這裡是不是再想上面已經可以實作斷線重連的增量同步了,還要運作ID幹嘛?其實還有一種情況沒考慮,就是當主伺服器當機後,某台從伺服器被選舉成為新的主伺服器,這種情況我們就通過比較運作ID來區分。

運作ID(run id)是伺服器啟動時自動生成的40個随機的十六進制字元串,主服務和從伺服器均會生成運作ID

當從伺服器首次同步主伺服器的資料時,主伺服器會發送自己的運作ID給從伺服器,從伺服器會儲存在RDB檔案中

當從伺服器斷線重連後,從伺服器會向主伺服器發送之前儲存的主伺服器運作ID,如果伺服器運作ID比對,則證明主伺服器未發生更改,可以嘗試進行增量同步

如果伺服器運作ID不比對,則進行全量同步

完整的psync過程非常的複雜,在2.8-4.0的主從複制版本中已經做到了非常完善。psync指令發送的參數如下:

psync <runid> <offset>

當從伺服器沒有複制過任何主伺服器(并不是主從第一次複制,因為主伺服器可能會變化,而是從伺服器第一次全量同步),從伺服器将會發送:

psync ? -1

Redis分布式基石——主從複制技術詳述
Redis分布式基石——主從複制技術詳述

從伺服器接收到SLAVEOF 127.0.0.1 6379指令

從伺服器傳回OK給指令發起方(這裡是異步操作,先傳回OK,再儲存位址和端口資訊)

從伺服器将IP位址和端口資訊儲存到Master Host和Master Port中

從伺服器根據Master Host和Master Port主動向主伺服器發起套接字連接配接,同時從服務将會未這個套接字連接配接關聯一個專門用于檔案複制工作的檔案事件處理器,用于後續的RDB檔案複制等工作

主伺服器接收到從伺服器的套接字連接配接請求,為該請求建立對應的套接字連接配接之後,并将從伺服器看着一個用戶端(在主從複制中,主伺服器和從伺服器之間其實互為用戶端和服務端)

套接字連接配接建立完成,從伺服器主動向主服務發送PING指令,如果在指定的逾時時間内主伺服器傳回PONG,則證明套接字連接配接可用,否則斷開重連

如果主伺服器設定了密碼(masterauth),那麼從伺服器向主伺服器發送AUTH masterauth指令,進行身份驗證。注意,如果從伺服器發送了密碼,主服務并未設定密碼,此時主服務會發送no password is set錯誤;如果主伺服器需要密碼,而從伺服器未發送密碼,此時主伺服器會發送NOAUTH錯誤;如果密碼不比對,主伺服器會發送invalid password錯誤。

從伺服器向主伺服器發送REPLCONF listening-port xxxx(xxxx表示從伺服器的端口)。主伺服器接收到該指令後會将資料儲存起來,當用戶端使用INFO replication查詢主從資訊時能夠傳回資料

從伺服器發送psync指令,此步驟請檢視上圖psync的兩種情況

主伺服器與從伺服器之間互為用戶端,進行資料的請求/響應

主伺服器與從伺服器之間通過心跳包機制,判斷連接配接是否斷開。從伺服器每個1秒向主伺服器發送指令,REPLCONF ACL offset(從伺服器的複制偏移量),該機制可以保證主從之間資料的正确同步,如果偏移量不相等,主伺服器将會采取增量/全量同步措施來保證主從之間資料狀态一緻(增量/全量的選擇取決于,offset+1的資料是否仍在複制積壓緩沖區中)

Redis 2.8-4.0版本仍然有一些改進的空間,當主伺服器切換時,是否也能進行增量同步呢?是以Redis 4.0版本針對這個問題做了優化處理,psync更新為psync2.0。

psync2.0 抛棄了伺服器運作ID,采用了replid和replid2來代替,其中replid存儲的是目前主伺服器的運作ID,replid2儲存的是上一個主伺服器運作ID。

主伺服器運作id(replid)

上個主伺服器運作id(replid2)

通過replid和replid2我們可以解決主伺服器切換時,增量同步的問題:

如果replid等于目前主伺服器的運作id,那麼判斷同步方式增量/全量同步

如果replid不相等,則判斷replid2是否相等(是否同屬于上一個主伺服器的從伺服器),如果相等,仍然可以選擇增量/全量同步,如果不相等則隻能進行全量同步。

本文閱讀參考了黃健宏老師著作《Redis設計與實作》、老錢著作《Redis深度曆險》等資料

繼續閱讀