Redis的資料複製

真正的飛魚發表於2022-12-21

介紹 Redis 的複製

Redis 的複製功能分為同步(sync)和命令傳播(command propagate)這兩個操作

  • 同步操作用於,將從伺服器的資料庫狀態更新至主伺服器當前所處的資料庫狀態;
  • 命令傳播操作用於,在主伺服器的資料庫狀態被修改,導致主從伺服器的資料庫狀態出現不一致時,讓主從伺服器的資料庫重新回到一致狀態。

如果主從伺服器雙方的資料庫儲存相同的資料,我們稱主從伺服器的資料庫狀態一致

當從伺服器第一次連線主伺服器時,Redis 使用全量複製進行資料同步。

當從伺服器在斷線後重新連線主伺服器時,Redis 使用增量複製進行資料同步。

完整重同步

全量複製,也被稱為完整重同步。

當客戶端向從伺服器傳送 slaveof 命令,要求從伺服器複製主伺服器時,從伺服器首先需要執行同步操作,將從伺服器的資料庫狀態更新至主伺服器當前所處的資料庫狀態。

從伺服器對主伺服器的完整重同步操作,需要透過向主伺服器傳送 psync 命令來完成。psync 的命令為:psync ? -1


psync 命令在完整重同步模式下的的執行步驟:讓主伺服器建立併傳送 RDB 檔案,以及主伺服器向從伺服器傳送儲存在緩衝區裡面的寫命令來進行同步。

  1. 從伺服器向主伺服器傳送 psync 命令。
  2. 主伺服器收到 psync 命令後,主伺服器執行 bgsave 命令,在後臺生成一個 RDB 檔案,並使用一個緩衝區(replication buffer)記錄從現在開始執行的所有寫命令。
  3. 主伺服器給從伺服器同步資料:當主伺服器的 bgsave 命令執行完畢時,主伺服器會將 bgsave 命令生成的 RDB 檔案傳送給從伺服器,從伺服器接收並載入這個 RDB 檔案,將自己的資料庫狀態更新至主伺服器執行 bgsave 命令時的資料庫狀態。
  4. 主伺服器給從伺服器傳送緩衝區裡面的所有寫命令:主伺服器將記錄在緩衝區裡面的所有寫命令傳送給從伺服器, 從伺服器執行這些寫命令,將自己的資料庫狀態更新至主伺服器資料庫當前所處的狀態。

需要注意的是:

從庫在開始和主庫進行資料複製前,可能儲存了其他資料。為了避免之前資料的影響,從庫在收到主庫傳送的 RDB 檔案後,會先把自己當前的資料庫清空。

1660827427693-db712d00-f509-452b-b3b3-0ca7b62f920d.jpeg

介紹 偏移量 & 積壓緩衝區 & 執行ID

部分重同步功能透過以下三個部分來實現:

  • 主伺服器的複製偏移量 和 從伺服器的複製偏移量(replication offset)
  • 主伺服器的複製積壓緩衝區(replication backlog buffer)
  • 伺服器的執行 ID(run ID)

複製偏移量

主伺服器和從伺服器會分別維護一個複製偏移量:

  • 主伺服器每次向從伺服器傳播 N 個位元組的資料時,就將自己的複製偏移量的值加上 N。
  • 從伺服器每次收到主伺服器傳播來的 N 個位元組的資料時,就將自己的複製偏移量的值加上 N。

透過對比主從伺服器的複製偏移量,程式可以很容易地知道主從伺服器是否處於一致狀態:

  • 如果主從伺服器兩者的偏移量總是相同,那麼說明主從伺服器處於一致狀態。
  • 如果主從伺服器兩者的偏移量並不相同,那麼說明主從伺服器並未處於一致狀態。

複製積壓緩衝區

複製積壓緩衝區(repl_backlog_buffer)是由主伺服器維護的一個固定長度的先進先出(FIFO)佇列。

固定指的是,當入隊元素的數量大於佇列長度時,最先入隊的元素會被彈出,而新元素會被放入佇列。或者理解複製積壓緩衝區為一個環形緩衝區。


當主伺服器進行命令傳播時,它不僅會將寫命令傳送給所有從伺服器,還會將寫命令入隊到複製積壓緩衝區裡面。

1666749197788-0b40c1d8-a332-4981-af8d-d5e1999e1c91.png


因此,主伺服器的複製積壓緩衝區裡面會儲存著一部分最近傳播的寫命令,並且複製積壓緩衝區會為佇列中的每個位元組記錄相應的複製偏移量。

1666749670434-061f6a1f-2d53-4eaf-ae6c-b4eb694ff613.png

當從伺服器在斷線後重新連線主伺服器時,從伺服器會透過 psync 命令將自己的複製偏移量 offset 傳送給主伺服器,主伺服器會根據這個複製偏移量來決定對從伺服器執行完整重同步還是部分重同步操作:

  • 如果 offset 偏移量之後的資料(也即是偏移量 offset+1 開始的資料)仍然存在於複製積壓緩衝區裡面,那麼主伺服器將對從伺服器執行部分重同步操作。
  • 如果 offset 偏移量之後的資料已經不存在於複製積壓緩衝區,那麼主伺服器將對從伺服器執行完整重同步操作。

複製積壓緩衝區的大小

Redis 為複製積壓緩衝區設定的預設大小為 1MB,如果主伺服器需要執行大量的寫命令,又或者主從伺服器斷線後重連線所需的時間比較長,那麼這個大小也許並不合適。我們可以透過 repl-backlog-size 選項修改複製積壓緩衝區的大小。

如果複製積壓緩衝區的大小設定得不恰當,那麼 psync 命令的部分重同步複製就不能正常發揮作用。因此,正確估算和設定複製積壓緩衝區的大小非常重要。

為了保證主從伺服器斷線並重連線後可以使用部分重同步功能,我們需要保證複製積壓緩衝區的大小足夠大。複製積壓緩衝區的最小大小可以根據公式 second * write_size_per_second 來估算:

  • second 是從伺服器斷線後重新連線上主伺服器所需的平均時間(以秒計算)。
  • write_size_per_second 是主伺服器平均每秒產生的寫命令資料量(協議格式的寫命令的長度總和)。

例如,如果主伺服器平均每秒產生1 MB的寫資料,而從伺服器斷線之後平均要 5 秒才能重新連線上主伺服器,那麼複製積壓緩衝區的大小就不能低於 5 MB。

為了安全起見,可以將複製積壓緩衝區的大小設為: 2 * second * write_size_per_second,這樣可以保證絕大部分斷線情況都能用部分重同步來處理。

伺服器執行 ID

每個 Redis 伺服器,不論主伺服器還是從服務,都會有自己的執行 ID。執行 ID 在伺服器啟動時自動生成,由 40 個隨機的十六進位制字元組成,例如:53b9b28df8042fdc9ab5e3fcbbbabff1d5dce2b3。


當從伺服器對主伺服器進行初次複製時,主伺服器會將自己的執行 ID 傳送給從伺服器,而從伺服器會將主伺服器的這個執行 ID 儲存起來。 當從伺服器斷線並重新連上一個主伺服器時,從伺服器將向當前連線的主伺服器傳送之前儲存的主伺服器的執行 ID:

  • 如果從伺服器儲存的主伺服器的執行 ID 和當前連線的主伺服器的執行 ID 相同,那麼說明從伺服器斷線之前複製的就是當前連線的這個主伺服器, 主伺服器可以繼續嘗試執行部分重同步操作。
  • 如果從伺服器儲存的主伺服器的執行 ID 和當前連線的主伺服器的執行 ID 並不相同,那麼說明從伺服器斷線之前複製的主伺服器並不是當前連線的這個主伺服器,主伺服器將對從伺服器執行完整重同步操作。

部分重同步

增量複製,也被稱為部分重同步。

在 Redis 中,從庫對主庫的複製可以分為以下兩種情況:

  • 初次複製:從庫以前沒有複製過任何主庫,或者從庫當前要複製的主伺服器和上一次複製的主伺服器不同。
  • 網路斷線重連後複製:處於命令傳播階段的主從庫因為網路原因而中斷了複製,但從庫透過自動重連線重新連上了主庫,並繼續複製主服。

在 Redis 2.8 之前,如果主從庫在命令傳播時出現了網路中斷,那麼在斷線重連後,從庫會和主庫重新進行一次全量複製,開銷非常大。

從 2.8 版本開始,Redis 引入了部分重同步功能。部分重同步指的是,從伺服器只同步主伺服器的部分資料。當從伺服器在斷線後重新連線主伺服器時,如果條件允許,主伺服器可以將主從伺服器連線斷開期間執行的寫命令傳送給從伺服器,從伺服器只要接收並執行這些寫命令,就可以將資料庫更新至主伺服器當前所處的狀態。


執行部分重同步是有前提條件的。

  • offset 偏移量
  • 執行 ID

當從伺服器對主伺服器進行初次複製時,主伺服器會將自己的執行 ID 傳送給從伺服器,而從伺服器會將主伺服器的這個執行 ID 儲存起來。 當從伺服器斷線並重新連上一個主伺服器時,從伺服器會透過 psync 命令將自己的複製偏移量 offset 和 之前儲存的主伺服器的執行 ID 傳送給主伺服器。

主伺服器會根據這個複製偏移量 和 執行ID 來決定對從伺服器執行完整重同步還是部分重同步操作:

  • 如果從伺服器儲存的主伺服器的執行 ID 和當前連線的主伺服器的執行 ID 相同,那麼說明從伺服器斷線之前複製的就是當前連線的這個主伺服器, 主伺服器可以繼續嘗試執行部分重同步操作。
  • 如果從伺服器儲存的主伺服器的執行 ID 和當前連線的主伺服器的執行 ID 並不相同,那麼說明從伺服器斷線之前複製的主伺服器並不是當前連線的這個主伺服器,主伺服器將對從伺服器執行完整重同步操作。
  • 如果 offset 偏移量之後的資料(也即是偏移量 offset+1 開始的資料)仍然存在於複製積壓緩衝區裡面,那麼主伺服器將對從伺服器執行部分重同步操作。
  • 如果 offset 偏移量之後的資料已經不存在於複製積壓緩衝區,那麼主伺服器將對從伺服器執行完整重同步操作。

從伺服器對主伺服器的部分重同步操作,需要透過向主伺服器傳送 psync 命令來完成。psync 命令為:psync < runID > < offset >

1666686614547-bc127175-8e4a-4715-a425-cbc4a6dda998.png

psync 命令

從伺服器對主伺服器的同步操作,需要透過向主伺服器傳送 psync 命令來完成。

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

  • 完整重同步用於,處理初次複製情況;
  • 部分重同步用於,處理斷線後重複製情況:當從伺服器在斷線後重新連線主伺服器時,如果條件允許,主伺服器可以將主從伺服器連 接斷開期間執行的寫命令傳送給從伺服器,從伺服器只要接收並執行這 些寫命令,就可以將資料庫更新至主伺服器當前所處的狀態。

psync 命令的呼叫方法有兩種:

  • 如果從伺服器以前沒有複製過任何主伺服器,或者之前執行過 slaveof no one 命令,那麼從伺服器在開始一次新的複製時將向主伺服器傳送 psync ? -1 命令,主動請求主伺服器進行完整重同步。
  • 如果從伺服器已經複製過某個主伺服器,那麼從伺服器在開始一次新的複製時將向主伺服器傳送 psync 命令:其中 runid 是上一次複製的主伺服器的執行 ID,而 offset 則是從伺服器當前的複製偏移量,接收到這個命令的主伺服器會透過這兩個引數來判斷應該對從伺服器執行哪種同步操作。

根據情況,接收到 psync 命令的主伺服器會向從伺服器返回以下三種回覆的其中一種:

  • 如果主伺服器返回 +fullresync 回覆,那麼表示主伺服器將與從伺服器執行完整重同步操作:其中 runid 是這個主伺服器的執行 ID,從伺服器會將這個 ID 儲存起來,在下一次傳送 psync 命令時使用;而 offset 則是主伺服器當前的複製偏移量,從伺服器會將這個值作為自己的初始化偏移量。
  • 如果主伺服器返回 +continue 回覆,那麼表示主伺服器將與從伺服器執行部分重同步操作,從伺服器只要等著主伺服器將自己缺少的那部分資料傳送過來就可以了。
  • 如果主伺服器返回 -err 回覆,那麼表示主伺服器的版本低於 Redis2.8,它識別不了 psync 命令,從伺服器將向主伺服器傳送 sync 命令,並與主伺服器執行完整同步操作。

命令傳播

主伺服器透過向從伺服器傳播命令來更新從伺服器的狀態,保持主從伺服器一致。

當完成了同步之後, 主從伺服器就會進入命令傳播階段, 這時主伺服器只要一直將自己執行的寫命令傳送給從伺服器, 而從伺服器只要一直接收並執行主伺服器發來的寫命令, 就可以保證主從伺服器一直保持一致了。

主伺服器進行命令傳播時,它不僅會將寫命令傳送給所有從伺服器,還會將寫命令入隊到複製積壓緩衝區裡面。

心跳檢測

從伺服器透過向主伺服器傳送命令來進行心跳檢測,以及命令丟失檢測。

在命令傳播階段,從伺服器預設會以每秒一次的頻率,向主伺服器傳送命令:replconf ack <replication_offset>。其中 replication_offset 是從伺服器當前的複製偏移量。

傳送 replconf ack 命令對於主從伺服器有三個作用:

  • 檢測主從伺服器的網路連線狀態。
  • 輔助實現 min-slaves 選項。
  • 檢測命令丟失。

檢測主從伺服器的網路連線狀態。

主從伺服器可以透過傳送和接收 replconf ack 命令來檢查兩者之間的網路連線是否正常:如果主伺服器超過一秒鐘沒有收到從伺服器發來的 replconf ack 命令,那麼主伺服器就知道主從伺服器之間的連線出現問題了。

透過向主伺服器傳送 info replication 命令,在列出的從伺服器列表的 lag 一欄中,我們可以看到相應從伺服器最後一次向主伺服器傳送 replconf ack 命令距離現在過了多少秒。在一般情況下,lag 的值應該在 0 秒或者 1 秒之間跳動,如果超過 1 秒的話,那麼說明主從伺服器之間的連線出現了故障。


輔助實現 min-slaves 選項。

Redis 的 min-slaves-to-write 和 min-slaves-max-lag 兩個選項可以防止主伺服器在不安全的情況下執行寫命令。

舉個例子,如果我們向主伺服器提供以下設定:

  • min-slaves-to-write 3
  • min-slaves-max-lag 10

那麼在從伺服器的數量少於 3 個,或者 3 個從伺服器的延遲(lag)值都 ≥ 10 秒時,主伺服器將拒絕執行寫命令,這裡的延遲值就是上面提到的 info replication 命令的 lag 值。


檢測命令丟失。

如果因為網路故障,主伺服器傳播給從伺服器的寫命令在半路丟失,那麼當從伺服器向主伺服器傳送 replconf ack 命令時,主伺服器將發覺從伺服器當前的複製偏移量少於自己的複製偏移量,然後主伺服器就會根據從伺服器提交的複製偏移量,在複製積壓緩衝區裡面找到從伺服器缺少的資料,並將這些資料重新傳送給從伺服器。

參考資料

《Redis設計與實現》

相關文章