讀配置、講原理、看面試真題,我只能幫你到這了。。。

語言: CN / TW / HK

當我在寫 一上來就主從、叢集、哨兵,這誰受得了 的時候,好多小夥伴就迫不及待的留言想看這些模式了,今天我們就從配置檔案、設計原理、面試真題三個方面來聊一聊 Redis 的主從複製。

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

主從複製,從 5.0.0 版本開始,Redis 正式將 SLAVEOF 命令改名成了 REPLICAOF 命令並逐漸廢棄原來的 SLAVEOF 命令

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

主從拓撲架構

master 用來寫操作,replicas 用來讀取資料,適用於讀多寫少的場景。而對於寫併發量較高的場景,多個從節點會導致主節點寫命令的多次傳送從而過度消耗網路頻寬,同時也加重了 master 的負載影響服務穩定性。

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

當 master 需要多個 replica 時,為了避免對 master 的性能干擾,可以採用樹狀主從結構降低主節點的壓力。

為了讓大家對概念有更清晰的認識,我們先來看一下配置檔案中主從複製的引數介紹:

REPLICATION

replicaof <masterip> <masterport>

通過設定 master 的 ip 和 port ,可以使當前的 Redis 例項成為另一臺 Redis 例項的副本。在 Redis 啟動時,它會自動從 master 進行資料同步。

  • Redis 複製是非同步的,可以通過修改 master 的配置,在 master 沒有與給定數量的 replica 連線時,主機停止接收寫入;

  • 如果複製鏈路丟失的時間相對較短,Redis replica 可以與 master 執行部分重新同步,可以使用合理的 backlog 值來進行配置( 見下文 );

  • 複製是自動的,不需要使用者干預。在網路分割槽後,replica 會自動嘗試重新連線到 master 並與 master 重新同步;

masterauth <master-password>

當 master 設定了密碼保護時,replica 服務連線 master 的密碼

replica-serve-stale-data yes

當 replica 與 master 失去連線或者主從複製在進行時,replica 可以有兩種不同的設定:

  • replica-serve-stale-data:yes(預設值),則 replica 仍將響應客戶端請求,可能會有過期資料,或者如果這是第一次同步,則資料集可能為空。

  • replica-serve-stale-data:no , replica 將對所有請求命令(但不包含 INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG, SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB, COMMAND, POST, HOST: and LATENCY)返回 SYNC with master in progress 的錯誤。

replica-read-only

可以將 replica 配置為是否只讀,yes 代表為只讀狀態,將會拒絕所有寫入命令;no 表示可以寫入。 從 Redis 2.6 之後, replica 支援只讀模式且預設開啟。可以在執行時使用 CONFIG SET 來隨時開啟或者關閉。

對 replica 進行寫入可能有助於儲存一些 臨時 資料(因為寫入 replica 的資料在與 master 重新同步後很容易被刪除),計算慢速集或排序集操作並將其儲存到本地金鑰是多次觀察到的可寫副本的一個用例。但如果客戶端由於配置錯誤而向其寫入資料,則也可能會導致問題。

級聯結構 中即使 replica B 節點是可寫的,Sub-replica C 也不會看到 B 的寫入,而是將擁有和 master A 相同的資料集。

設定為 yes 並不表示客戶端用叢集方式以 replica 為入口連入叢集時,不可以進行 set 操作,且 set 操作的資料不會被放在 replica 的槽上,會被放到某 master 的槽上。

注意:只讀 replica 設計的目的不是為了暴露於網際網路上不受信任的客戶端,它只是一個防止例項誤用的保護層。預設情況下,只讀副本仍會匯出所有管理命令,如 CONFIG、DEBUG 等。在一定程度上,可以使用 rename-command 來隱藏所有管理/危險命令,從而提高只讀副本的 安全性

repl-diskless-sync

複製同步策略:磁碟(disk)或套接字(socket),預設為 no 使用 disk 。

新的 replicas 和重新連線的 replicas 如果因為接收到差異而無法繼續複製過程,則需要執行“完全同步”。RDB 檔案從 master 傳送到 replicas,傳輸可以通過兩種不同的方式進行:

  1. Disk-backed:Redis master 節點建立一個新的程序並將 RDB 檔案寫入 磁碟 ,然後檔案通過父程序增量傳輸給 replicas 節點;

  2. Diskless:Redis master 節點建立一個新的程序並直接將 RDB 檔案寫入到 replicas 的 sockets 中,不寫到磁碟。

  • 當進行 disk-backed 複製時, RDB 檔案生成完畢,多個 replicas 通過 排隊 來同步 RDB 檔案。

  • 當進行 diskless 複製時,master 節點會等待一段時間(下邊的 repl-diskless-sync-delay 配置)再傳輸以期望會有多個 replicas 連線進來,這樣 master 節點就可以 同時同步 到多個 replicas 節點。如果超出了等待時間,則需要排隊,等當前的 replica 處理完成之後在進行下一個 replica 的處理。

硬碟效能差,網路效能好的情況下 diskless 效果更佳

警告:無盤複製目前處於試驗階段

repl-diskless-sync-delay

當啟用 diskless 複製後,可以通過此選項設定 master 節點建立子程序前等待的時間,即延遲啟動資料傳輸,目的可以在第一個 replica 就緒後,等待更多的 replica 就緒。單位為秒,預設為 5 秒

repl-ping-replica-period

Replica 傳送 PING 到 master 的間隔,預設值為 10 秒。

repl-timeout

預設值 60 秒,此選項用於設定以下情形的 timeout 判斷:

  • 從 replica 節點的角度來看的 SYNC 過程中的 I/O 傳輸 —— 沒有收到 master SYNC 傳輸的 rdb snapshot 資料;

  • 從 replica 節點的角度來看的 master 的 timeout(如 data,pings)—— replica 沒有收到 master 傳送的資料包或者 ping;

  • 從 master 節點角度來看的 replica 的 timeout(如 REPLCONF ACK pings)—— master 沒有收到 REPLCONF ACK 的確認資訊;需要注意的是,此選項必須大於 repl-ping-replica-period,否則在 master 和 replica 之間存在低業務量的情況下會經常發生 timeout。

repl-disable-tcp-nodelay

master 和 replicas 節點的連線是否關掉 TCP_NODELAY 選項。

  • 如果選擇“yes”,Redis 將使用更少的 TCP 資料包和更少的頻寬向 replicas 傳送資料。但這會增加資料在 replicas 端顯示的延遲,對於使用預設配置的 Linux 核心,延遲可達 40 毫秒。

  • 如果選擇“no”,則資料出現在 replicas 端的延遲將減少,但複製將使用更多頻寬。

這個實際影響的是 TCP 層的選項,裡面會用 setsockopt 設定,預設為 no,表示 TCP 層會禁用 Nagle 演算法,儘快將資料發出, 設定為 yes 表示 TCP 層啟用 Nagle 演算法,資料累積到一定程度,或者經過一定時間 TCP 層才會將其發出。

預設情況下,我們會針對低延遲進行優化,但在流量非常高的情況下,或者當 master 和 replicas 距離多個 hops 時,將此選項改為“yes”可能會更好。

repl-backlog-size

設定複製的 backlog 緩衝大小,預設 1mb。backlog 是一個緩衝區,當 replica 斷開一段時間連線時,它會累積 replica 資料,所以當 replica 想要再次重新連線時,一般不需要 全量同步 ,只需要進行 部分同步 即可,只傳遞 replica 在斷開連線時丟失的部分資料。

更大的 backlog 緩衝大小,意味著 replicas 斷開重連後,依然可以進行續傳的時間越長(支援斷開更長時間)。

backlog 緩衝只有在至少一個 replica 節點連過來的時候 master 節點才需要建立。

repl-backlog-ttl

當 replicas 節點斷開連線後,master 節點會在一段時間後釋放 backlog 緩衝區。 這個選項設定的是當 最後一個 replica 斷開連結後,master 需要等待多少秒再釋放緩衝區。預設 3600 秒,0 表示永遠不釋放。

replicas 節點永遠都不會釋放這個緩衝區,因為它有可能再次連線到 master 節點, 然後嘗試進行 “增量同步”。

replica-priority

replica-priority 是 Redis 通過 INFO 介面釋出的整數,預設值為 100。 當 master 節點無法正常工作後 Redis Sentinel 通過這個值來決定將哪個 replica 節點提升為 master 節點。這個數值 越小 表示 越優先 進行提升。如有三個 replica 節點其 priority 值分別為 10,100,25, Sentinel 會選擇 priority 為 10 的節點進行提升。這個值為 0 表示 replica 節點永遠 不能 被提升為 master 節點。

min-replicas-to-write

min-replicas-max-lag

//表示要求至少3個延遲<=10秒的副本存在min-replicas-to-write 3  //下文中的 Nmin-replicas-max-lag 10 //下文中的 M

複製程式碼

從 Redis 2.8 開始,如果連線的 replica 延遲小於或等於 M 秒的個數少於 N 個(N 個 replica 需要處於“online”狀態),則 master 可能停止接受寫入並回復 error。由於 Redis 使用非同步複製,因此無法確保 replica 是否實際接收到給定的寫命令,因此總會有一個數據丟失視窗。

原理如下:

  • replica 每秒鐘都會 ping master,確認已處理的複製流的數量;

  • master 會記得上一次從每個 replica 都收到 ping 的時間,延遲就是根據 master 從 replica 接收的最後一次 ping 計算的;

  • 使用者可以配置延遲不超過最大秒數的最小 replica 數;

此選項不保證 N 個副本將接受寫入,但在沒有足夠的副本可用的情況下,將丟失寫入的暴露視窗限制在指定的秒數內。

N 預設值為 0,M 預設值為 10。任意一個設定為 0 表示不啟用此功能。

replica-announce-ip 5.5.5.5

replica-announce-port 1234

Redis master 可以通過不同方式列出連線上來的 replicas 節點的地址和埠。 如 Redis Sentinel 等會使用 “INFO replication” 命令來獲取 replica 例項資訊,master 的“ROLE“ 命令也會提供此資訊。

這個資訊一般來說是通過 replica 節點通過以下方式獲取然後報告上來的:

  • IP:通過自動識別連線到 Socket 的資訊自動獲取

  • Port:一般來說這個值就是 replicas 節點用來接受客戶端的連線的監聽埠

但是,若啟用了埠轉發或者 NAT,可能需要其他地址和端口才能連線到 replicas 節點。 這種情況下,需要設定這兩個選項,這樣 replicas 就會用這兩個選項設定的值覆蓋預設行為獲取的值,然後報告給 master 節點。 根據實際情況,你可以只設置其中某個選項,而不用兩個選項都設定。

配置的介紹到這裡就結束了,接下來我們把上邊提到的概念串起來,聊一下主從複製的相關原理。

原理

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

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

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

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

Redis 複製功能是如何工作的

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

複製偏移量即使在沒有一個 replica 連線到 master 時,也會自增,所以基本上每一對給定的 Replication ID, offset 都會標識一個 master 資料集的確切版本。

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

說到這兒,那什麼是全量同步,那什麼又是增量同步呢?

全量同步

  1. replica 連線 master,傳送 PSYNC 命令;

  2. master 執行 bgsave 開啟一個後臺儲存程序,以便於生產一個 RDB 檔案。同時它開始緩衝所有從客戶端接收到的新的寫入命令。

  3. 當後臺儲存完成時, master 將資料集檔案傳輸給所有的 replica,並在傳送期間繼續記錄被執行的寫命令;

  4. replica 收到 RDB 檔案之後,丟棄所有的舊資料,然後載入新檔案到記憶體;

  5. replica 載入完成後,通知 master 傳送所有緩衝的命令給 replica,這個過程以指令流的形式完成並且和 Redis 協議本身的格式相同;

  6. replica 開始接收命令請求,並執行來自 master 緩衝區的寫命令。

注意:SYNC 是一箇舊協議,在新的 Redis 中已經不再被使用,但是仍然向後相容。因為它不允許部分重同步,所以現在 PSYNC 被用來替代 SYNC。

正常情況下,一個全量重同步要求在磁碟上建立一個 RDB 檔案,然後將它從磁碟載入進記憶體,然後 replica 以此進行資料同步。如果磁碟效能很低的話,這對 master 是一個壓力很大的操作。Redis 2.8.18 是第一個支援無磁碟複製的版本。在此設定中,子程序直接傳送 RDB 檔案給 replica 的 sockets 中,無需使用磁碟作為中間儲存介質。

增量同步

master 把命令傳送給所有的 replica 的同時,還會將命令寫入 backlog 緩衝區裡面。

當 replica 與 master 斷開連線又重新連線之後,此時要判斷 replica 的偏移量與 master 的偏移量的差集有沒有超過 backlog 的大小,

  • 如果沒有則給 replica 傳送 CONTINUE,等待 master 將 backlog 中的資料傳送給 replica;

  • 如果超過了則返回 FULLRESYNC runid offset,replica 將 runid 儲存起來,並進行全量同步;

最後我們來聊幾個在面試過程中經常提到的面試題。

面試題

在主從複製過程中,關閉 master 的持久化會引發什麼問題呢?

資料會從 master 和所有 replica 中被刪除。我們用案例來說明一下:

  1. 我們設定節點 A 為 master 並關閉它的持久化設定,設定節點 B 和 C 為 replica;

  2. 當 master 崩潰時,由於系統中配置了自動重啟的指令碼,此時 master 會自動重啟。但是由於持久化被關閉了,master 重啟後其資料集合為空;

  3. 此時,如果 replica 從 master 中同步資料,就會導致 replica 中的資料也會變為空集合。

因此,我們在使用 Redis 複製功能時, 強烈建議 在 master 和 replica 中啟用持久化。如果因為非常慢的磁碟效能導致的延遲問題而不啟用持久化時, 應該配置節點來避免重置後自動重啟

Redis 複製如何處理 key 的過期問題

Redis 的過期機制可以限制 key 的生存時間,該機制取決於 Redis 計算時間的能力。但是,即使使用 Lua 指令碼將這些 key 變為過期的 key,Redis replicas 也能正確地複製這些 key。

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

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

  • 由於主驅動的原因,master 無法及時提供 DEL 命令,所以有時候 replica 的記憶體中仍然可能存在邏輯上已經過期的 key。為了處理這個問題,replica 使用它的邏輯時鐘來報告在不違反資料一致性的前提下,讀取操作的 key 不存在。用這種方法,replica 避免報告邏輯過期的 key 仍然存在。在實際應用中,使用 replica 程式進行擴充套件的 HTML 碎片快取,將避免返回已經比期望的時間更早的資料項。

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

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

阿 Q 將持續更新 java 實戰方面的文章,如果你有不同的意見或者更好的 idea,歡迎聯絡阿 Q。

【阿 Q 說程式碼】,值得關注的公眾號

文章風格多變,配圖通俗易懂,故事生動有趣,來聊聊技術呀!