簡單來說:
增量同步就是Master 只把比 Slave 新的數據發給 Slave,而不是發送全部數據。它像一個持續更新的直播流,或者我之前比喻的“每日更新期刊”。Slave 不用重新加載所有數據,只需要接收和應用這些新的更新。
這就像,你作家寫的書,讀者已經拿到了完整的最新版。你以后每天寫點新東西,只需要把寫的新章節發給他,他就能把新的章節加到書的后面,就不用我把整本書再重新打印一遍然后麻煩他再讀一遍了。
為什么需要增量同步?
想象一下,你寫一部百萬字的小說,每天寫幾百字。如果每次粉絲(Slave)斷線(比如手機信號不好,或者中途APP閃退了一下),然后他再連回來,你都得把這幾百萬字的完整小說重新給他背一遍,那得多麻煩?流量、時間和資源都會大量浪費。
增量同步就是為了解決這個問題:減少全量同步的次數,提高復制效率。
增量同步的核心部件:
在 Redis 中,實現增量同步有兩個關鍵的協同工作部分:
-
Master 的
Replication Backlog
(復制積壓緩沖區):- 這是一個特殊的“草稿箱”。你(作家 Master)每寫好一句(執行一條寫命令),除了發送給粉絲(如果有粉絲連接著),你還會把它暫時副本放在這個“草稿箱”里。
- 這個草稿箱是環形的,就像一個循環錄像帶。它有一個固定的大小(可以配置,默認是 1MB),當寫的內容越來越多時,最老的內容就會被新內容覆蓋掉。
- 這個草稿箱就是為了當你和粉絲(Master 和 Slave)的直播連接意外斷開后,再重新連接時,可以方便地找到“從哪個位置(
offset
)開始發送新的更新”。
-
Replication Offset
(復制偏移量):- 這是一個“閱讀進度條”。你把寫好的內容一句一句地發出去了,你會記錄你已經發了多少字(Master 的最新偏移量)。
- 你的每個粉絲(Slave)也會記錄自己已經收到了多少字。
- 這個偏移量是一個單調遞增的數字。它精確標識了主服務器數據流中某個點。Master 知道自己發到了哪里,Slave 知道自己收到了哪里。
-
Master Replication ID
(replid/runid):- 你的“作家筆名 ID”(或者你的身份證號)。在你沒換筆名(Master 非重啟,或者故障轉移)的情況下,即使你暫停寫作(數據有一段時間沒更新),在“筆名”不變的前提下,你上次的寫作“風格”(數據特征)也還在。
- 這是判斷 Master 身份唯一性的關鍵。只有 Master ID 沒變動,增量同步才有基礎。
增量同步是如何進行的?
-
Slave 斷線重連時(比如因網絡波動):
- 粉絲(Slave)與你(Master)的電話斷了。但是,他知道自己上次是“鬼才金庸”(Master
replid
)的讀者,而且已經看完了第 130 章(offset
)。 - 當連接恢復后,粉絲立即打電話(發送
PSYNC <replid> <offset>
命令)給你,并說:“我是老粉絲,筆名是‘鬼才金庸’,我上次看到第 130 章了。”
- 粉絲(Slave)與你(Master)的電話斷了。但是,他知道自己上次是“鬼才金庸”(Master
-
Master 的判斷:
- 第一步:檢查筆名 ID(replid)。你(Master)發現電話里說的筆名 ID 和你當前使用的筆名 ID 是一模一樣的。
- “嗯,沒換筆名,還是我的老讀者!”
- 第二步:檢查閱讀進度 (offset) 是否在草稿箱 (Backlog) 里。 你(Master)馬上去檢查你的“草稿箱”(Replication Backlog)。
- 你發現你的草稿箱里保存了從第 120 章到當前你最新寫的第 140 章的所有內容。
- 而粉絲小明說他看到第 130 章了。
- “太好了!你說的 130 章后面的那點更新(第 131 章到第 140 章我最新寫的部分),都在我的草稿箱里呢,沒被新的內容擠出去!”
- 第一步:檢查筆名 ID(replid)。你(Master)發現電話里說的筆名 ID 和你當前使用的筆名 ID 是一模一樣的。
-
增量發送:
- 你(Master)立刻告訴粉絲:“好啦,第 131 章到第 140 章的更新,我現在就報給你聽!” (Master 回復
+CONTINUE
,然后直接從Replication Backlog
中提取offset
130之后的數據,以 AOF 命令流的形式發送給 Slave。) - 粉絲(Slave)收到后,將其追加到自己的小說集后面,并更新自己的閱讀進度(
offset
)。
- 你(Master)立刻告訴粉絲:“好啦,第 131 章到第 140 章的更新,我現在就報給你聽!” (Master 回復
動圖模擬增量同步:
假設 offset
代表數據寫入進程,Master 從 0 開始不斷增加。
Replication Backlog 是一個固定大小的窗口,里面保存了最近的歷史數據。
時間軸 -->Master 數據寫入: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...]
Master 當前 Offset: 11Replication Backlog (假設大小為 5): [ 7, 8, 9, 10, 11 ] ^ 最老 ^ 最新Slave A (上次收到 9): 連接 - PSYNC <replid> 9Master 檢查: replid 匹配,9 在 Backlog 內。?Master 回復: +CONTINUEMaster 發送增量: [10, 11]
Slave A 更新到 11。成功增量同步!Slave B (上次收到 5):連接 - PSYNC <replid> 5Master 檢查: replid 匹配,但 5 不在 Backlog 內 [7, 8, 9, 10, 11]。?Master 回復: +FULLRESYNC ...Master 執行: 全量同步 (生成 RDB, 再發送從新 RDB 點開始的 AOF)
Slave B 更新到 11。被迫全量同步!
需要注意:
Replication Backlog
的大小配置很關鍵。 如果這個緩沖區太小,Slave 稍微掉隊一點(斷線時間稍長一點),其offset
就會超出緩沖區范圍,導致增量同步失敗,仍然退化為全量同步。所以需要根據網絡情況和應用的寫入 QPS(每秒查詢率)來合理估算和設置這個積壓緩沖區的大小。通常建議設置為 Slave 在斷線重連前可能積累的最大寫命令量。- 如果 Master 本身發生了重啟(導致
runid
改變),或者發生了故障轉移(Failover),新的 Master 會有新的runid
,即使它們的offset
看起來一致,Slave 也會被判斷為需要進行全量同步。