Redis 學習筆記(篇九):主從複製
Redis 中,可以通過執行 savleof 命令或者設定 slaveof 選項,讓一個伺服器去複製另一個伺服器,我們稱被複制的伺服器為主伺服器,而對主伺服器進行復制的伺服器則被稱為從伺服器。
Redis 2.8 之前複製功能的實現
Redis 中的複製分為同步和命令傳播兩個操作。
- 同步操作是將從伺服器的資料庫狀態更新值主伺服器當前所處的資料庫狀態。
- 命令傳播操作則用於在主伺服器的資料庫狀態被修改,導致主從伺服器的資料庫出現不一致時,讓主從伺服器的資料庫重新回到一致狀態。
同步
當客戶端向從伺服器傳送 slaveof 命令,要求從伺服器複製主伺服器時,從伺服器首先需要執行同步操作,也即是,將從伺服器的資料庫狀態更新值主伺服器當前所處的資料庫狀態。
從伺服器對主伺服器的同步操作是通過向主伺服器傳送 sync 命令來完成,執行步驟如下:
- 從伺服器向主伺服器傳送 sync 命令。
- 收到 psync 命令的主伺服器執行 bgsave 命令,在後臺生成一個 RDB 檔案,並用一個緩衝區記錄從現在開始執行的所有寫命令。
- 當主伺服器 BGSAVE 命令執行完畢時,主伺服器會將 BGSAVE 命令生成的 RDB 檔案傳送給從伺服器,從伺服器接收並載入這個 RDB 檔案,將自己的資料庫狀態更新至主伺服器執行 BGSAVE 命令時的資料庫狀態。
- 主伺服器將記錄在緩衝區裡面的所有寫命令傳送給從伺服器,從伺服器執行這些寫命令,將自己的資料庫狀態更新至主伺服器資料庫當前所處的狀態。
sync 命令執行期間,主從伺服器的通訊過程如下圖所示(出自《Redis設計與實現第二版》第十五章:複製):
命令傳播
命令傳播是指:在同步之後,主伺服器會將自己執行的寫命令,傳送給從伺服器執行,當從伺服器執行了相同的寫命令之後,主從伺服器將再次回到一致狀態。
Redis 2.8 之前複製功能的缺陷
在 Redis 中,從伺服器對主伺服器的複製分為兩種情況:
- 初次複製:從伺服器以前沒有複製過主伺服器。
- 斷線後重新複製:從伺服器因為網路原因中斷了複製,但從伺服器通過自動重連線重新連線上了主伺服器,並繼續複製主伺服器。
對於初次複製來說,舊版複製功能能夠很好的完成任務,但對於斷線後重新複製來說,舊版複製功能雖然也能讓主從伺服器資料一致,但效率卻非常低(需要載入主伺服器的整個快照)。
Redis 2.8 之後複製功能的實現
為了解決舊版複製功能在處理斷線重新複製情況下的低效問題,Redis 從 2.8 版本開始,使用 psync 命令代替了 sync 命令來執行復制時的同步操作。
psync 命令具有完整重同步和部分重同步兩種模式。
- 完整重同步用於處理初次複製情況:完整步驟和 sync 命令的步驟基本一樣。
- 部分重同步則用於處理斷線後重新複製的情況:當從伺服器重新連線主伺服器時,如果條件允許,主伺服器可以將從伺服器連線斷開期間執行的寫命令傳送給從伺服器,從伺服器只要接收並執行這些寫命令,就可以將資料庫資料更新至和主伺服器一樣。
部分重同步的執行過程如下圖所示(出自《Redis設計與實現第二版》第十五章:複製):
部分重同步的實現
部分重同步功能包含三個部分:
- 主伺服器的複製偏移量和從伺服器的複製偏移量。
- 主伺服器的複製積壓緩衝區。
- 伺服器的執行 ID。
複製偏移量
執行復制的雙方——主伺服器和從伺服器會分別維護一個複製偏移量。
- 主伺服器每次向從伺服器傳播 N 個位元組時,就將自己的複製偏移量 + N。
- 從伺服器每次收到主伺服器傳來的 N 個位元組的資料時,就將自己的複製偏移量 + N。
對比主從伺服器的複製偏移量可知:
- 如果主從伺服器的複製偏移量相等,則說明主從伺服器資料一致;
- 相反,如果主從伺服器兩者的偏移量不等,則說明主從伺服器中的資料不一致。
複製積壓緩衝區
複製積壓緩衝區是由主伺服器維護的一個固定長度的先進先出(FIFO)佇列,預設大小為 1MB。
當主伺服器進行命令傳播時,不僅會將寫命令傳送給所有從伺服器,還會將寫命令入隊到複製積壓緩衝區裡面。
因此,主伺服器的複製積壓緩衝區裡面會儲存著一部分最近傳播的寫命令,並且複製積壓緩衝區會為佇列中的每個位元組記錄相應的複製偏移量。
當從伺服器重新連上主伺服器時,從伺服器會通過 PSYNC 命令將自己的複製偏移量, offset 傳送給主伺服器,主伺服器會根據這個複製偏移量來決定對從伺服器執行何種同步操作:
- 如果 offset 偏移量之後的資料(也即是偏移量 offset + 1 開始的資料)仍然存在於複製積壓緩衝區裡面,那麼主伺服器將對從伺服器執行部分重同步操作。
- 相反,如果 offset 偏移量之後的資料已經不存在於複製積壓緩衝區,那麼主伺服器將對從伺服器執行完整重同步操作。
注意:需要調整複製積壓緩衝區的大小為:2 * second * write_size_per_second
- 其中 second 為從伺服器斷線後重新連線上主伺服器所需的平均時間。
- write_size_per_second 是主伺服器平均每秒產生的寫命令資料量。
伺服器執行 ID
- 每個 Redis 伺服器,不論主伺服器還是從伺服器都有自己的執行 ID。
- 執行 ID 在伺服器啟動時自動生成。
當從伺服器對主伺服器進行初次複製時,主伺服器會將自己的執行 ID 傳送給從伺服器,而從伺服器會將這個執行 ID 儲存起來,
當從伺服器斷線並重新連上主伺服器時,從伺服器將向當前連線的主伺服器傳送之前儲存的執行 ID:
- 如果從伺服器儲存的執行 ID 和當前連線的主伺服器的執行 ID 相同,那麼說明從伺服器斷線之前複製的就是當前連線的這個主伺服器,主伺服器可以繼續嘗試執行部分重同步操作。
- 如果從伺服器儲存的執行 ID 和當前連線的主伺服器的執行 ID 不相同,那麼說明從伺服器斷線之前複製的主伺服器不是當前連線的這個主伺服器,主伺服器將對從伺服器執行完整重同步操作。
心跳檢測
在命令傳播階段,從伺服器預設會以每秒一次的頻率,向主伺服器傳送命令:
REAPCONF ACK <replication_offset>
其中 replication_offset 是代表從伺服器當前的複製偏移量。
傳送 REAPCONF ACK 命令對於主從伺服器有三個作用:
- 檢測主從伺服器的網路連線狀態。
- 輔助實現 min-slaves 選項。
- 檢測命令丟失。
檢測主從伺服器的網路連線狀態
主從伺服器可以通過傳送和接受 REAPCONF ACK 命令來檢查兩者之間的網路連線是否正常:如果主伺服器超過一秒沒有收到從伺服器發來的 REAPCONF ACK 命令,那麼主伺服器就知道主從伺服器的連接出現問題了。
輔助實現 min-slaves 選項
Redis 的 min-slaves-to-write 和 min-slaves-max-lag 另個選項可以防止主伺服器在不安全的情況下執行寫命令。
比如,如果我們向主伺服器提供以下設定:
min-slaves-to-write 3
min-slaves-max-lag 10
那麼在從伺服器的數量少於3個或者3個從伺服器的延遲(lag)值都大於或等於 10s 時,主伺服器將拒絕執行寫命令。
檢測命令丟失
如果因為網路故障,主伺服器傳播給從伺服器的寫命令在半路丟失,那麼當從伺服器向主伺服器傳送 REAPCONF ACK 命令時,主伺服器將發覺從伺服器當前的複製偏移量少於自己的複製偏移量,然後主伺服器就會根據從伺服器提交的複製偏移量,在複製積壓緩衝區裡面找到從伺服器缺少的資料,並將這些資料重新發送給從伺服器