1. 程式人生 > >一個關於Redis的主從複製的詳解(讓你瞭解你不知道的東西)

一個關於Redis的主從複製的詳解(讓你瞭解你不知道的東西)

主從複製

在 Redis 複製的基礎上,使用和配置主從複製非常簡單,能使得從 Redis 伺服器(下文稱 slave)能精確得複製主 Redis 伺服器(下文稱 master)的內容。每次當 slave 和 master 之間的連線斷開時, slave 會自動重連到 master 上,並且無論這期間 master 發生了什麼, slave 都將嘗試讓自身成為 master 的精確副本。

這個系統的執行依靠三個主要的機制:

  • 當一個 master 例項和一個 slave 例項連線正常時, master 會發送一連串的命令流來保持對 slave 的更新,以便於將自身資料集的改變複製給 slave , :包括客戶端的寫入、key 的過期或被逐出等等。

  • 當 master 和 slave 之間的連線斷開之後,因為網路問題、或者是主從意識到連線超時, slave 重新連線上 master 並會嘗試進行部分重同步:這意味著它會嘗試只獲取在斷開連線期間內丟失的命令流。

  • 當無法進行部分重同步時, slave 會請求進行全量重同步。這會涉及到一個更復雜的過程,例如 master 需要建立所有資料的快照,將之傳送給 slave ,之後在資料集更改時持續傳送命令流到 slave 。

Redis使用預設的非同步複製,其特點是高延遲和高效能,是絕大多數 Redis 用例的自然複製模式。但是,從 Redis 伺服器會非同步地確認其從主 Redis 伺服器週期接收到的資料量。

客戶端可以使用 WAIT 命令來請求同步複製某些特定的資料。但是,WAIT 命令只能確保在其他 Redis 例項中有指定數量的已確認的副本:在故障轉移期間,由於不同原因的故障轉移或是由於 Redis 永續性的實際配置,故障轉移期間確認的寫入操作可能仍然會丟失。你可以檢視 Sentinel 或 Redis 叢集文件,瞭解關於高可用性和故障轉移的更多資訊。本文的其餘部分主要描述 Redis 基本複製功能的基本特性。

接下來的是一些關於 Redis 複製的非常重要的事實:

  • Redis 使用非同步複製,slave 和 master 之間非同步地確認處理的資料量

  • 一個 master 可以擁有多個 slave

  • slave 可以接受其他 slave 的連線。除了多個 slave 可以連線到同一個 master 之外, slave 之間也可以像層疊狀的結構(cascading-like structure)連線到其他 slave 。自 Redis 4.0 起,所有的 sub-slave 將會從 master 收到完全一樣的複製流。

  • Redis 複製在 master 側是非阻塞的。這意味著 master 在一個或多個 slave 進行初次同步或者是部分重同步時,可以繼續處理查詢請求。

  • 複製在 slave 側大部分也是非阻塞的。當 slave 進行初次同步時,它可以使用舊資料集處理查詢請求,假設你在 redis.conf 中配置了讓 Redis 這樣做的話。否則,你可以配置如果複製流斷開, Redis slave 會返回一個 error 給客戶端。但是,在初次同步之後,舊資料集必須被刪除,同時載入新的資料集。 slave 在這個短暫的時間視窗內(如果資料集很大,會持續較長時間),會阻塞到來的連線請求。自 Redis 4.0 開始,可以配置 Redis 使刪除舊資料集的操作在另一個不同的執行緒中進行,但是,載入新資料集的操作依然需要在主執行緒中進行並且會阻塞 slave 。

  • 複製既可以被用在可伸縮性,以便只讀查詢可以有多個 slave 進行(例如 O(N) 複雜度的慢操作可以被下放到 slave ),或者僅用於資料安全。

  • 可以使用複製來避免 master 將全部資料集寫入磁碟造成的開銷:一種典型的技術是配置你的 master Redis.conf 以避免對磁碟進行持久化,然後連線一個 slave ,其配置為不定期儲存或是啟用 AOF。但是,這個設定必須小心處理,因為重新啟動的 master 程式將從一個空資料集開始:如果一個 slave 試圖與它同步,那麼這個 slave 也會被清空。

當 master 關閉持久化時,複製的安全性

在使用 Redis 複製功能時的設定中,強烈建議在 master 和在 slave 中啟用持久化。當不可能啟用時,例如由於非常慢的磁碟效能而導致的延遲問題,應該配置例項來避免重置後自動重啟

為了更好地理解為什麼關閉了持久化並配置了自動重啟的 master 是危險的,檢查以下故障模式,這些故障模式中資料會從 master 和所有 slave 中被刪除:

  1. 我們設定節點 A 為 master 並關閉它的持久化設定,節點 B 和 C 從 節點 A 複製資料。

  2. 節點 A 崩潰,但是他有一些自動重啟的系統可以重啟程序。但是由於持久化被關閉了,節點重啟後其資料集合為空。

  3. 節點 B 和 節點 C 會從節點 A 複製資料,但是節點 A 的資料集是空的,因此複製的結果是它們會銷燬自身之前的資料副本。

當 Redis Sentinel 被用於高可用並且 master 關閉持久化,這時如果允許自動重啟程序也是很危險的。例如, master 可以重啟的足夠快以致於 Sentinel 沒有探測到故障,因此上述的故障模式也會發生。

任何時候資料安全性都是很重要的,所以如果 master 使用複製功能的同時未配置持久化,那麼自動重啟程序這項應該被禁用。

Redis 複製功能是如何工作的

每一個 Redis master 都有一個 replication ID :這是一個較大的偽隨機字串,標記了一個給定的資料集。每個 master 也持有一個偏移量,master 將自己產生的複製流傳送給 slave 時,傳送多少個位元組的資料,自身的偏移量就會增加多少,目的是當有新的操作修改自己的資料集時,它可以以此更新 slave 的狀態。複製偏移量即使在沒有一個 slave 連線到 master 時,也會自增,所以基本上每一對給定的

Replication ID, offset

都會標識一個 master 資料集的確切版本。

當 slave 連線到 master 時,它們使用 PSYNC 命令來發送它們記錄的舊的 master replication ID 和它們至今為止處理的偏移量。通過這種方式, master 能夠僅傳送 slave 所需的增量部分。但是如果 master 的緩衝區中沒有足夠的命令積壓緩衝記錄,或者如果 slave 引用了不再知道的歷史記錄(replication ID),則會轉而進行一個全量重同步:在這種情況下, slave 會得到一個完整的資料集副本,從頭開始。

下面是一個全量同步的工作細節:

master 開啟一個後臺儲存程序,以便於生產一個 RDB 檔案。同時它開始緩衝所有從客戶端接收到的新的寫入命令。當後臺儲存完成時, master 將資料集檔案傳輸給 slave, slave將之儲存在磁碟上,然後載入檔案到記憶體。再然後 master 會發送所有緩衝的命令發給 slave。這個過程以指令流的形式完成並且和 Redis 協議本身的格式相同。

你可以用 telnet 自己進行嘗試。在伺服器正在做一些工作的同時連線到 Redis 埠併發出 SYNC 命令。你將會看到一個批量傳輸,並且之後每一個 master 接收到的命令都將在 telnet 回話中被重新發出。事實上 SYNC 是一箇舊協議,在新的 Redis 例項中已經不再被使用,但是其仍然向後相容:但它不允許部分重同步,所以現在 PSYNC 被用來替代 SYNC。

之前說過,當主從之間的連線因為一些原因崩潰之後, slave 能夠自動重連。如果 master 收到了多個 slave 要求同步的請求,它會執行一個單獨的後臺儲存,以便於為多個 slave 服務。

無需磁碟參與的複製

正常情況下,一個全量重同步要求在磁碟上建立一個 RDB 檔案,然後將它從磁碟載入進記憶體,然後 slave以此進行資料同步。

如果磁碟效能很低的話,這對 master 是一個壓力很大的操作。Redis 2.8.18 是第一個支援無磁碟複製的版本。在此設定中,子程序直接傳送 RDB 檔案給 slave,無需使用磁碟作為中間儲存介質。

配置

配置基本的 Redis 複製功能是很簡單的:只需要將以下內容加進 slave 的配置檔案:

slaveof 192.168.1.1 6379

當然你需要用你自己的 master IP 地址(或者主機名)和埠替換掉 192.168.1.1 6379。另一種方法,你也可以使用 SLAVEOF 命令, master 會開啟一個跟 slave 間的同步。

還有一些引數用於調節記憶體中儲存的緩衝積壓部分(replication backlog),以便執行部分重同步。詳見 redis.conf 和 Redis Distribution 瞭解更多資訊。

無磁碟複製可以使用 repl-diskless-sync 配置引數。repl-diskless-sync-delay 引數可以延遲啟動資料傳輸,目的可以在第一個 slave就緒後,等待更多的 slave就緒。可以在 Redis Distribution 例子中的 redis.conf 中看到更多細節資訊。

只讀性質的 slave

自從 Redis 2.6 之後, slave 支援只讀模式且預設開啟。redis.conf 檔案中的 slave-read-only 變數控制這個行為,且可以在執行時使用 CONFIG SET 來隨時開啟或者關閉。

只讀模式下的 slave 將會拒絕所有寫入命令,因此實踐中不可能由於某種出錯而將資料寫入 slave 。但這並不意味著該特性旨在將一個 slave 例項暴露到 Internet ,或者更廣泛地說,將之暴露在存在不可信客戶端的網路,因為像 DEBUG 或者 CONFIG 這樣的管理員命令仍在啟用。但是,在 redis.conf 檔案中使用 rename-command 指令可以禁用上述管理員命令以提高只讀例項的安全性。

您也許想知道為什麼可以還原只讀設定,並有可以通過寫入操作來設定 slave 例項。如果 slave 跟 master 在同步或者 slave 在重啟,那麼這些寫操作將會無效,但是將短暫資料儲存在 writable slave 中還是有一些合理的用例的。

例如,計算 slow Set 或者 Sorted Set 的操作並將它們儲存在本地 key 中是多次觀察到的使用 writable slave 的用例。

但是注意,4.0 版本之前的 writable slaves 不能用生存時間來淘汰 key 。這意味著,如果你使用 EXPIRE 或者其他命令為 key 設定了最大 TTL 的話,你將會在鍵值計數(count of keys)中看到這個 key ,並且它還在記憶體中。所以總的來說,將 writable slaves 和設定過 TTL 的 key 混用將會導致問題。

Redis 4.0 RC3 及更高版本徹底解決了這個問題,現在 writable slaves 能夠像 master 一樣驅逐 TTL 設定過的 key 了,但 DB 編號大於 63(但預設情況下,Redis例項只有16個數據庫)的 key 除外。

另請注意,由於 Redis 4.0 writable slaves 僅能本地,並且不會將資料傳播到與該例項相連的 sub-slave 上。sub-slave 將總是接收與最頂層 master 向 intermediate slaves 傳送的複製流相同的複製流。所以例如在以下設定中:

A —> B —-> C

及時節點 B 是可寫的,C 也不會看到 B 的寫入,而是將擁有和 master 例項 A 相同的資料集。

設定一個 slave 對 master 進行驗證

如果你的 master 通過 requirepass 設定了密碼,則在所有同步操作中配置 slave 使用該密碼是很簡單的。

要在正在執行的例項上執行此操作,請使用 redis-cli 並輸入:

config set masterauth <password>

要永久設定的話,請將其新增到您的配置檔案中:

masterauth <password>

允許只寫入 N 個附加的副本

從Redis 2.8開始,只有當至少有 N 個 slave 連線到 master 時,才有可能配置 Redis master 接受寫查詢。

但是,由於 Redis 使用非同步複製,因此無法確保 slave 是否實際接收到給定的寫命令,因此總會有一個數據丟失視窗。

以下是該特性的工作原理:

  • Redis slave 每秒鐘都會 ping master,確認已處理的複製流的數量。
  • Redis master 會記得上一次從每個 slave 都收到 ping 的時間。
  • 使用者可以配置一個最小的 slave 數量,使得它滯後 <= 最大秒數。

如果至少有 N 個 slave ,並且滯後小於 M 秒,則寫入將被接受。

你可能認為這是一個盡力而為的資料安全機制,對於給定的寫入來說,不能保證一致性,但至少資料丟失的時間窗限制在給定的秒數內。一般來說,繫結的資料丟失比不繫結的更好。

如果條件不滿足,master 將會回覆一個 error 並且寫入將不被接受。

這個特性有兩個配置引數:

  • min-slaves-to-write <slave 數量>
  • min-slaves-max-lag <秒數>

有關更多資訊,請檢視隨 Redis 原始碼發行版一起提供的示例 redis.conf 檔案。

Redis 複製如何處理 key 的過期

Redis 的過期機制可以限制 key 的生存時間。此功能取決於 Redis 例項計算時間的能力,但是,即使使用 Lua 指令碼更改了這些 key,Redis slaves 也能正確地複製具有過期時間的 key。

為了實現這樣的功能,Redis 不能依靠主從使用同步時鐘,因為這是一個無法解決的並且會導致 race condition 和資料集不一致的問題,所以 Redis 使用三種主要的技術使過期的 key 的複製能夠正確工作:

  • slave 不會讓 key 過期,而是等待 master 讓 key 過期。當一個 master 讓一個 key 到期(或由於 LRU 演算法將之驅逐)時,它會合成一個 DEL 命令並傳輸到所有的 slave。

  • 但是,由於這是 master 驅動的 key 過期行為,master 無法及時提供 DEL 命令,所以有時候 slave 的記憶體中仍然可能存在在邏輯上已經過期的 key 。為了處理這個問題,slave 使用它的邏輯時鐘以報告只有在不違反資料集的一致性的讀取操作(從主機的新命令到達)中才存在 key。用這種方法,slave 避免報告邏輯過期的 key 仍然存在。在實際應用中,使用 slave 程式進行縮放的 HTML 碎片快取,將避免返回已經比期望的時間更早的資料項。

  • 在Lua指令碼執行期間,不執行任何 key 過期操作。當一個Lua指令碼執行時,從概念上講,master 中的時間是被凍結的,這樣指令碼執行的時候,一個給定的鍵要麼存在要麼不存在。這可以防止 key 在指令碼中間過期,保證將相同的指令碼傳送到 slave ,從而在二者的資料集中產生相同的效果。

一旦一個 slave 被提升為一個 master ,它將開始獨立地過期 key,而不需要任何舊 master 的幫助。

在 Docker 和 NAT 中配置複製

當使用 Docker 或其他型別的容器使用埠轉發或網路地址轉換時,Redis 複製需要特別小心,特別是在使用 Redis Sentinel 或其他系統(其中掃描 master INFO 或 ROLE 命令的輸出情況以便於發現 slave 地址的)。

問題是 ROLE 命令和 INFO 輸出的複製部分在釋出到 master 例項中時,將顯示 slave 具有的用於連線到 master 的 IP 地址,而在使用 NAT 的環境中,和 slave 例項的邏輯地址(客戶機用來連線 slave 的地址)相比較可能會不同。

類似地,slaves 將以 redis.conf 檔案中監聽的埠為序列出,在重新對映埠的情況下,該埠可能與轉發的埠不同。

為了解決這兩個問題,從 Redis 3.2.2 開始,可以強制一個 slave 向 master 通告一對任意的 IP 和埠。使用的兩個配置指令是:

slave-announce-ip 5.5.5.5

slave-announce-port 1234

在近期 Redis distributions 中的 redis.conf 的樣例中可以找到記錄。

INFO 和 ROLE 命令

有兩個 Redis 命令可以提供有關主從例項當前複製引數的很多資訊。一個是INFO。如果使用複製引數像 INFO replication 呼叫該命令,,則只顯示與複製相關的資訊。另一個更加 computer-friendly 的命令是 ROLE,它提供 master 和 slave 的複製狀態以及它們的複製偏移量,連線的 slaves 列表等等。

重新啟動和故障轉移後的部分重同步

從 Redis 4.0 開始,當一個例項在故障轉移後被提升為 master 時,它仍然能夠與舊 master 的 slaves 進行部分重同步。為此,slave 會記住舊 master 的舊 replication ID 和複製偏移量,因此即使詢問舊的 replication ID,其也可以將部分複製緩衝提供給連線的 slave 。

但是,升級的 slave 的新 replication ID 將不同,因為它構成了資料集的不同歷史記錄。例如,master 可以返回可用,並且可以在一段時間內繼續接受寫入命令,因此在被提升的 slave 中使用相同的 replication ID 將違反一對複製標識和偏移對只能標識單一資料集的規則。

另外,slave 在關機並重新啟動後,能夠在 RDB 檔案中儲存所需資訊,以便與 master 進行重同步。這在升級的情況下很有用。當需要時,最好使用 SHUTDOWN 命令來執行 slave 的儲存和退出操作。

想了解更多內容可以去Redis官網學習一下哦。