在 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 伺服器周期接收到的資料量。
接下來的是一些關于 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 也會被清空。
在使用 Redis 複制功能時的設定中,強烈建議在 master 和在 slave 中啟用持久化。當不可能啟用時,例如由于非常慢的磁盤性能而導緻的延遲問題,應該配置執行個體來避免重置後自動重新開機。
為了更好地了解為什麼關閉了持久化并配置了自動重新開機的 master 是危險的,檢查以下故障模式,這些故障模式中資料會從 master 和所有 slave 中被删除:
我們設定節點 A 為 master 并關閉它的持久化設定,節點 B 和 C 從 節點 A 複制資料。
節點 A 崩潰,但是他有一些自動重新開機的系統可以重新開機程序。但是由于持久化被關閉了,節點重新開機後其資料集合為空。
節點 B 和 節點 C 會從節點 A 複制資料,但是節點 A 的資料集是空的,是以複制的結果是它們會銷毀自身之前的資料副本。
當 Redis Sentinel 被用于高可用并且 master 關閉持久化,這時如果允許自動重新開機程序也是很危險的。例如, master 可以重新開機的足夠快以緻于 Sentinel 沒有探測到故障,是以上述的故障模式也會發生。
任何時候資料安全性都是很重要的,是以如果 master 使用複制功能的同時未配置持久化,那麼自動重新開機程序這項應該被禁用。
每一個 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 協定本身的格式相同。
之前說過,當主從之間的連接配接因為一些原因崩潰之後, slave 能夠自動重連。如果 master 收到了多個 slave 要求同步的請求,它會執行一個單獨的背景儲存,以便于為多個 slave 服務。
正常情況下,一個全量重同步要求在磁盤上建立一個 RDB 檔案,然後将它從磁盤加載進記憶體,然後 slave以此進行資料同步。
如果磁盤性能很低的話,這對 master 是一個壓力很大的操作。Redis 2.8.18 是第一個支援無磁盤複制的版本。在此設定中,子程序直接發送 RDB 檔案給 slave,無需使用磁盤作為中間儲存媒體。
配置基本的 Redis 複制功能是很簡單的:隻需要将以下内容加進 slave 的配置檔案:
slaveof 192.168.1.1 6379
還有一些參數用于調節記憶體中儲存的緩沖積壓部分(replication backlog),以便執行部分重同步。詳見 redis.conf 和 Redis Distribution 了解更多資訊。
無磁盤複制可以使用 repl-diskless-sync 配置參數。repl-diskless-sync-delay 參數可以延遲啟動資料傳輸,目的可以在第一個 slave就緒後,等待更多的 slave就緒。可以在 Redis Distribution 例子中的 redis.conf 中看到更多細節資訊。
隻讀模式下的 slave 将會拒絕所有寫入指令,是以實踐中不可能由于某種出錯而将資料寫入 slave 。但這并不意味着該特性旨在将一個 slave 執行個體暴露到 Internet ,或者更廣泛地說,将之暴露在存在不可信用戶端的網絡,因為像 DEBUG 或者 CONFIG 這樣的管理者指令仍在啟用。但是,在 redis.conf 檔案中使用 rename-command 指令可以禁用上述管理者指令以提高隻讀執行個體的安全性。
您也許想知道為什麼可以還原隻讀設定,并有可以通過寫入操作來設定 slave 執行個體。如果 slave 跟 master 在同步或者 slave 在重新開機,那麼這些寫操作将會無效,但是将短暫資料存儲在 writable slave 中還是有一些合理的用例的。
例如,計算 slow Set 或者 Sorted Set 的操作并将它們存儲在本地 key 中是多次觀察到的使用 writable slave 的用例。
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 相同的資料集。
如果你的 master 通過 requirepass 設定了密碼,則在所有同步操作中配置 slave 使用該密碼是很簡單的。
要在正在運作的執行個體上執行此操作,請使用 redis-cli 并輸入:
config set masterauth <password>
要永久設定的話,請将其添加到您的配置檔案中:
masterauth <password>
從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 執行個體計算時間的能力,但是,即使使用 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 或其他類型的容器使用端口轉發或網絡位址轉換時,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 的樣例中可以找到記錄。
有兩個 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 的儲存和退出操作。
本文作者:陳群
本文來自雲栖社群合作夥伴rediscn,了解相關資訊可以關注redis.cn網站。