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 中被刪除:

我們設置節點 A 為 master 並關閉它的持久化設置,節點 B 和 C 從 節點 A 複製數據。

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

節點 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 的保存和退出操作。


分享到:


相關文章: