Redis復制

Redis復制

????在Redis中,用戶可以通過執(zhí)行SALVEOF命令或者設置slaveof選項,讓一個服務器去復制(replace)另一個服務器,我們稱呼被復制的服務器為主服務器(master),而對主服務器進行復制的服務器稱為從服務器(slave)。
? ? 下面分別介紹舊版(Redis 2.8之前)和新版本兩種復制機制。

1 舊版本復制功能的實現(xiàn)

? Redis的復制功能分為同步(sync)和命令傳播(command propagate)兩個操作:

? ? **同步操作用于將從服務器的數(shù)據(jù)庫狀態(tài)更新至主服務器當前的所處的服務器狀態(tài)。

? ?**同步傳播操作則用于在主服務器數(shù)據(jù)庫狀態(tài)被修改,導致主從服務器狀態(tài)不一致時,讓主從服務器的數(shù)據(jù)庫重新回到一致狀態(tài)。

(1) 同步操作

?????? 當客戶端向從服務器發(fā)送SLAVEOF命令時,要求服務器復制主服務器時,從服務器首先需要執(zhí)行同步操作,也即是將從服務器的數(shù)據(jù)庫狀態(tài)更新至主服務器狀態(tài)當前數(shù)據(jù)庫狀態(tài)。

?????從服務器對主服務器的同步操作是通過向主服務器發(fā)送SYNC命令來完成,以下是SYNC命令的執(zhí)行步驟:

?????? 1) 從服務器向主服務器發(fā)送SYNC命令。

?????? 2) 收到SYNC命令的主服務器執(zhí)行BGSAVE命令,在后臺生成一個RDB文件,并使用一個緩沖區(qū)記錄從現(xiàn)在開始執(zhí)行的所以寫命令。這里的緩沖區(qū)和AOF重寫的緩沖區(qū)的作用相似,因為后臺在生成RDB文件的時候,主服務器可以繼續(xù)處理命令。

?????? 3) 當主服務器的BGSAVE命令執(zhí)行完畢時,主服務器會將BGSAVE命令生成的RDB文件發(fā)送給從服務器,從服務器接收并載入這個RDB文件,將自己的數(shù)據(jù)庫狀態(tài)更新至主服務器BGSAVE命令時的數(shù)據(jù)庫狀態(tài)。

?????? 4) 主服務器將記錄在緩沖區(qū)的里面的所有寫命令發(fā)送給從服務器,從服務器執(zhí)行這些寫命令,將自己的數(shù)據(jù)庫狀態(tài)更新至主服務器當前所處的狀態(tài)。

主從服務器執(zhí)行SYNC命令期間的通信過程

(2) 命令傳播

?????? 在同步操作執(zhí)行完畢之后,主從服務器兩者的數(shù)據(jù)庫狀態(tài)達到一致狀態(tài),但是這種一致狀態(tài)并不是一成不變的,每當主服務器執(zhí)行客戶端發(fā)送的寫命令時,主服務器的數(shù)據(jù)庫狀態(tài)就有可能被修改,并導致主從服務器的狀態(tài)不再一致。

?????? 為了能讓主從服務器的裝態(tài)再次回到一致狀態(tài),主服務器需要對從服務器執(zhí)行命令傳播操作:主服務器會將自己執(zhí)行的寫命令,也就是造成從服務器不一致的那條命令,發(fā)送給從服務器執(zhí)行,當從服務器執(zhí)行了相同的命令之后,主從服務器的狀態(tài)再次回到一致。

3 舊版本復制功能的缺陷

? 在Redis中,從服務器對主服務器的復制可以分為兩種情況:

?????? (1) 初次復制:從服務器以前沒有復制過任何其他服務器,或者從服務器當前要復制的主服務器和上次復制的主服務器不一致。

?????? (2) 斷線后重復制:處于命令傳播階段的主從服務器因為網(wǎng)絡原因而中斷了復制,但從服務器通過自動重連重新連接上了主服務器,并繼續(xù)復制主服務器。

?????? 對于初次復制來說,舊版本能夠很好的完成任務,但對于斷線后重復制來說,雖然也能讓主從服務器重新回到一致性狀態(tài),但是效率非常低。

?????? 舊版本斷線重連后重新復制效率問題:如果在斷線期間,如果主服務器繼續(xù)接收客戶端的寫命令,使得主服務器的數(shù)據(jù)庫狀態(tài)發(fā)生了變化,這時重新連接上從服務器數(shù)據(jù)庫狀態(tài)和主服務器的數(shù)據(jù)庫狀態(tài)不一致。所以從服務器重新發(fā)送SYNC命令,開始同步操作。如果從服務器斷線前就有大量的數(shù)據(jù),斷線時間又非常短,假設主服務器在從服務器斷線狀態(tài)僅僅只增加了一個鍵的數(shù)據(jù),舊版復制依然會選擇重新生成RDB文件,可是主從服務器僅僅只有一個鍵的數(shù)據(jù)差異,之前的數(shù)據(jù)對于從服務器來說是沒有必要進行重新復制的,同時SYNC命令是一個非常消耗資源的操作,所以舊版復制對于斷線重連復制存在效率問題。

?每次執(zhí)行SYNC命令,主從服務器需要執(zhí)行以下動作:

????1) 主服務器需要執(zhí)行BGSAVE命令來生成RDB文件,這個生成操作會耗費主服務大量的CPU、 內(nèi)存和磁盤I/O資源。

????2)主服務器需要將自己生成的RDB文件發(fā)送給從服務器,這個發(fā)送操作會耗赍主從服務器大量的網(wǎng)絡資源(帶寬和流量),并對主服務器響應命令請求的時間產(chǎn)生影響。

????3)接收到RDB文件的從服務器需要載入主服務器發(fā)來的RDB文件,并且在載入 期間,從服務器會因為阻塞而沒辦法處理命令請求。

????因為SYNC命令是一個如此耗費資源的操作, 所以Redis有必要保證在真正有需要 時才執(zhí)行SYNC命令。


4 新版復制功能的實現(xiàn)

?為了解決舊版復制功能在斷行復制情況的低效問題,Redis從2.8版本開始,使用PSYNC命令代替SYNC命令來執(zhí)行復制的同步操作。

? PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)兩種模式。

? (1) 其中完整重同步用于處理初次復制情況:完整重同步的執(zhí)行步驟和SYNC命令的執(zhí)行步驟基本一樣,它們都是通過讓主服務器創(chuàng)建并發(fā)送RDB文件,以及向從服務器 發(fā)送保存在緩沖區(qū)里面的寫命令來進行同步。

? (2) 而部分重同步用于處理斷線后重復制的情況:當服務器在斷線后重新連接主服務器時,如果條件允許(并不是一定會執(zhí)行部分重同步,具體的后面會說),主服務器可以將主服務器連接斷開期間執(zhí)行的寫命令發(fā)送給服務器,從服務器接收并執(zhí)行這些寫命令,就可以將數(shù)據(jù)庫更新至主服務器當前的數(shù)據(jù)庫狀態(tài)。

?????? 執(zhí)行SYNC命令需要生成、傳送和載入整個RDB文件,而部分重同步只需將從服務器缺少的寫命令發(fā)送給從服務器執(zhí)行即可。相比于SYNC命令,PSYNC命令在執(zhí)行部分重同步所需的資源更加少,完成同步的速度也快的多。

5 部分重同步的實現(xiàn)

部分重同步功能由以下三個部分構(gòu)成:

?????? **主服務器的復制偏移量(replication offset)和從服務器的復制偏移量。

?????? **主服務器的復制積壓緩沖區(qū)(replication backlog)。

?????? **服務器的運行ID。

??(1)復制偏移量

? ? 執(zhí)行復制的雙方,主服務器和從服務器會分別維護一個復制偏移量:

?????? **主服務器每次向從服務器傳播N個字節(jié)的數(shù)據(jù),就將自己的復制偏移量的值加上N。

?????? **從服務器每次收到主服務器傳播來的N個字節(jié)數(shù)據(jù)時,就將自己的復制偏移量的值加上N。

?????? 某時刻主從服務器的數(shù)據(jù)庫處于一致狀態(tài),如下圖所示


主從服務器數(shù)據(jù)庫狀態(tài)處于一致狀態(tài)

????如果這個時候主服務器向三個服務器傳播33個字節(jié)的數(shù)據(jù),那么主服務器的復制偏移量將更新為10086 + 33 = 10119,而三個從服務器在接收到主服務器傳播的數(shù)據(jù)后,也會將復制的偏移量更新為10019。


更新后的主從服務器一致狀態(tài)

? ??通過對比主從服務器的復制偏移量,程序可以很容易的知道主從服務器是否處于一致狀態(tài)。

? ? 考慮以下的情況,如果主從服務器的復制偏移量都是10086,但是就在主服務器要向從服務器傳播長度為33字節(jié)的數(shù)據(jù)前,從服務器A斷線了,那么主服務傳播的數(shù)據(jù)只要從服務器B和C能收到,在這之后,主服務器、從服務器B和從服務器C三個服務器的復制偏移量都將更新為10119,而斷線的從服務器A的地址偏移量仍然停留在10086,這說明從服務器A與主服務器并不一致。


斷線導致主從服務器數(shù)據(jù)庫狀態(tài)不一致

????假設從服務器A在斷線之后就立即重新連接主服務器,并且成功,那么接下來,從服務器將向主服務器發(fā)送PSYNC 命令,報告從服務器 A 當前的復制偏移量為10086, 那么這時,主服務器應該對從服務器執(zhí)行完整重同步還是部分重同步呢?如果執(zhí)行部分重同步的話,主服務器又如何補償從服務器A在斷線期間丟失的那部分數(shù)據(jù)呢?這些都和復制積壓緩沖區(qū)有關(guān),下面看復制積壓緩沖區(qū)。

??(2)復制積壓緩沖區(qū)

?????? 復制積壓緩沖區(qū)是由主服務器維護一個固定長度(fixed-size)先進先出(FIFO)隊列,默認大小是1MB。所謂固定大小的意思是當隊列滿了時,又有新的數(shù)據(jù)入隊,最先入隊的元素會被彈出,而新的元素會被放入隊尾。

?????? 當服務器進行命令傳播時,它不僅會將寫命令發(fā)送給所有從服務器,還會將寫命令入隊到復制積壓緩沖區(qū)里。

主服務器向復制積壓緩沖區(qū)和所有從服務器傳播寫命令

????因此,主服務器的復制積壓緩沖區(qū)里還保存著一部分最近傳播的寫命令,并且復制積壓緩沖區(qū)會為隊列的每個字節(jié)記錄相應的復制偏移量,當從服務器重新連上主服務器時,從服務器會通過PSYNC命令將自己的復制偏移量offset發(fā)送給主服務器,主服務器會根據(jù)這個復制偏移量來決定對從服務器執(zhí)行何種同步操作:

????????1) 如果offset偏移量之后的數(shù)據(jù)仍然存在于復制積壓緩沖區(qū)里,即從服務器斷線期間主服務所有的寫命令都還在復制積壓緩沖區(qū)里,那么主服務器對從服務器執(zhí)行部分重同步操作。

?????? 2)相反,如果offset偏移量之后的數(shù)據(jù)已經(jīng)不完全存在于復制積壓緩沖區(qū),那么主服務將對從服務器執(zhí)行完整重同步操作,這里就是斷線重新連接不是一定執(zhí)行部分重同步的情況。


主服務器向從服務器發(fā)送斷線后缺失的數(shù)據(jù)

?????? 所以,對于上面斷線的從服務器A,當它重新連接上主服務器時,并向主服務器發(fā)送PSYNC命令,報告自己的復制偏移量為10086。

????????主服務收到從服務器的PSYNC命令以及偏移量10086后,主服務器將檢查偏移量10086之后的數(shù)據(jù)是否還存在于復制積壓緩沖區(qū)里,如果發(fā)現(xiàn)這些數(shù)據(jù)還存在,于是主服務器向從服務器發(fā)送+CONTINUE回復,表示數(shù)據(jù)同步將以部分重同步模式進行。

? ??????接著主服務器會將復制積壓緩沖區(qū)10086偏移量之后的所有數(shù)據(jù)都發(fā)送給從服務器。

從服務器只要接收這33個字節(jié)的卻是數(shù)據(jù),就可以回到與主服務器一致的狀態(tài)。



根據(jù)需要調(diào)整復制積壓緩沖區(qū)的大小:

??Redis為復制積壓緩沖區(qū)設置的默認大小為1MB,如果主服務器需要執(zhí)行大量的寫命令,又或者主從服務器斷線后重連的時間比較長,那么需要根據(jù)實際情況調(diào)整復制積壓緩沖區(qū)的大小。如果復制積壓緩沖區(qū)的大俠設置的不恰當,那PSYNC命令的重同步模式就不能正常發(fā)揮作用。

? 復制積壓緩沖區(qū)的最小大小可以根據(jù)公式second * write_size_per_second來估算:

?? 其中,second表示從服務器斷線后重連上主服務器所需的平均時間。

??????? writer_size_per_size則是主服務器平均每秒產(chǎn)生的寫命令的數(shù)據(jù)量。

?? 為了安全起見,可以將復制積壓緩沖區(qū)的大小設置為最小大小的2倍,這樣可以保證絕大部分斷線情況都能使用部分重同步來處理。


?(3)服務器運行ID

?????? 處理復制偏移量和復制積壓緩沖區(qū)之外,實現(xiàn)部分重同步還需用到服務器運行ID(run ID)。

?????? 每個Redis服務,無論是主服務器還是從服務器,在啟動時會自動生成自己的運行ID。當從服務器初次復制時,主服務器會將自己的運行ID傳送給從服務器,而從服務器會將這個運行ID保存起來。

?????? 當從服務器服務器斷線并重新連上一個主服務器時,從服務器會向當前連接的主服務器發(fā)送之前保存的運行ID,如果從服務器保存的運行ID和當前連接的主服務器的ID一致,那么說明從服務器斷線之前復制的就是當前連接的這個主服務器,主服務器可以繼續(xù)嘗試執(zhí)行部分重同步操作。

?????? 相反的,如果從服務器保存的運行ID和當前連接的主服務器的ID不一致,那么說明從服務器斷線前復制的主服務器并不是當前連接的這個主服務器,主服務器將會執(zhí)行完整的重同步操作。

5 小結(jié)

? (1) Redis 2.8以前的復制功能不能高效的處理斷線后重復制的情況,但Redis 2.8新添加的部分重復制功能可以解決這個問題。

? (2) 部分重復制通過復制偏移量、復制積壓緩沖區(qū)、服務器運行ID三個部分實現(xiàn)。

? (3) 復制積壓緩沖的大小會影響PSYNC命令的復制重同步模式,在實際情況下,最小復制緩沖區(qū)大小應根據(jù)主服務器每秒平均產(chǎn)生的寫命令和從服務器斷線重連的時間設置,同時為了安全起見,可以將復制積壓緩沖區(qū)大小設置為估算的最小緩沖區(qū)大小的2倍。



? ? 注:本文參考《Redis設計與實現(xiàn)》,如發(fā)現(xiàn)錯誤,請指正!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容