在分佈式環境中,數據副本 (Replica)
和複製 (Replication)
做爲提高系統可用性和讀寫性能的有效手段被大量應用系統設計中,Redis
也不例外。Redis
做爲單機數據庫使用時,適用常見有限且存在單點宕機問題,沒法維持高可用。所以 Redis
容許經過 SLAVEOF
命令或者 slaveof
配置項來讓一個 Redis server
複製另外一個 Redis server
的數據集和狀態,咱們稱之爲主從複製,主服務器下文稱 master
,從服務器下文稱 slave
,Redis
採用異步的複製機制。html
從 Redis 2.6
到 4.0
開發人員對複製流程進行逐步的優化,如下是演進過程:程序員
2.8
版本以前 Redis 複製採用 sync
命令,不管是第一次主從複製仍是斷線重連後再進行復制都採用全量同步,成本高2.8
~ 4.0
之間複製採用 psync
命令,這一特性主要添加了 Redis
在斷線重連時候可經過 offset
信息使用部分同步4.0
版本以後也採用 psync
,相比於 2.8
版本的 psync
優化了增量複製,這裏咱們稱爲 psync2
,2.8
版本的 psync
能夠稱爲 psync1
咱們先介紹 psync1
的實現,再介紹 psync2
的優化點,至於舊版 sync
的機制本文再也不贅述。redis
爲了解決舊版 SYNC
在處理斷線重連複製場景下的低效問題,Redis 2.8
採用 PSYNC
代替 SYNC
命令。PSYNC
命令具備全量同步和部分同步兩種模式。數據庫
前者和 SYNC
大體相同,都是讓 master
生成併發送 RDB
文件,而後再將保存在緩衝區中的寫命令傳播給 slave
來進行同步,至關於只有同步和命令傳播兩個階段。服務器
部分同步適用於斷線重連以後的同步,slave
只須要接收斷線期間丟失的寫命令就能夠,不須要進行全量同步。爲了實現部分同步,引入了複製偏移量(offset)
、複製積壓緩衝區(replication backlog buffer)
和運行 ID (run_id)
三個概念。併發
執行主從複製的雙方都會分別維護一個複製偏移量,master
每次向 slave
傳播 N
個字節,本身的複製偏移量就增長 N
;同理 slave
接收 N
個字節,自身的複製偏移量也增長 N
。經過對比主從之間的複製偏移量就能夠知道主從間的同步狀態。異步
複製積壓緩衝區是 master
維護的一個固定長度的 FIFO
隊列,默認大小爲 1MB
。當 master
進行命令傳播時,不只將寫命令發給 slave
還會同時寫進複製積壓緩衝區,所以 master
的複製積壓緩衝區會保存一部分最近傳播的寫命令。當 slave
重連上 master
時會將本身的複製偏移量經過 PSYNC 命令發給 master
,master
檢查本身的複製積壓緩衝區,若是發現這部分未同步的命令還在本身的複製積壓緩衝區中的話就能夠利用這些保存的命令進行部分同步,反之若是斷線過久這部分命令已經不在複製緩衝區中了,那沒辦法只能進行全量同步。分佈式
使人疑惑的是上述邏輯看似已經很圓滿了,這個 run_id
是作什麼用呢?其實這是由於 master
可能會在 slave
斷線期間發生變動,例如可能超時失去聯繫或者宕機致使斷線重連的是一個嶄新的 master
,再也不是斷線前複製的那個了。天然嶄新的 master
沒有以前維護的複製積壓緩衝區,只能進行全量同步。所以每一個 Redis server
都會有本身的運行 ID
,由 40
個隨機的十六進制字符組成。當 slave
初次複製 master
時,master 會將本身的運行 ID 發給 slave
進行保存,這樣 slave
函數
重連時再將這個運行 ID
發送給重連上的 master
,master
會接受這個 ID
並於自身的運行 ID
比較進而判斷是不是同一個 master
。性能
若是 slave
之前沒有複製過任何 master,或者以前執行過 SLAVEOF NO ONE
命令,那麼 slave
在開始一次新的複製時將向主服務器發送 PSYNC ? -1
命令,主動請求 master
進行完整重同步(由於這時不可能執行部分重同步)。相反地,若是 slave
已經複製過某個 master,那麼 slave
在開始一次新的複製時將向 master
發送 PSYNC runid offset
命令:其中 runid
是上一次複製的 master
的運行 ID
,而 offset
則是 slave
當前的複製偏移量,接收到這個命令的 master
會經過這兩個參數來判斷應該對 slave
執行哪一種同步操做。
根據狀況,接收到 PSYNC
命令的 master
會向 slave
返回如下三種回覆的其中一種:
若是 master
返回 +FULLRESYNC runid offset
回覆,那麼表示 master
將與 slave
執行完整重同步操做:其中 runid 是這個 master
的運行 ID
,slave
會將這個 ID
保存起來,在下一次發送 PSYNC
命令時使用;而 offset 則是 master
當前的複製偏移量,slave
會將這個值做爲本身的初始化偏移量
若是 master
返回 +CONTINUE
回覆,那麼表示 master
將與 slave
執行部分同步操做,slave
只要等着 master
將本身缺乏的那部分數據發送過來就能夠了
若是 master
返回 -ERR
回覆,那麼表示 master
的版本低於 Redis 2.8
,它識別不了 psync
命令,slave
將向 master
發送 SYNC
命令,並與 master
執行完整同步操做
因而可知 psync
也有不足之處,當 slave
重啓之後 master
runid
發生變化,也就意味者 slave
仍是會進行全量複製,而在實際的生產中進行 slave
的維護不少時候會進行重啓,而正是有因爲全量同步須要 master
執行快照,以及數據傳輸會帶不小的影響。所以在 4.0
版本,psync
命令作了改進,咱們稱之爲 psync2
。
Redis 4.0
版本新增 混合持久化,還優化了 psync
(如下稱 psync2
),psync2
最大的變化支持兩種場景下的部分重同步,一個場景是 slave
提高爲 master
後,其餘 slave
能夠重新提高的 master
進行部分重同步,這裏須要 slave
默認開啓複製積壓緩衝區;另一個場景就是 slave
重啓後,能夠進行部分重同步。這裏要注意和 psync1
的運行 ID
相比,這裏的複製 ID
有不同的意義。
Redis 4.0
引入另一個變量 master_replid 2
來存放同步過的 master
的複製 ID,同時複製 ID 在 slave
上的意義不一樣於以前的運行 ID,複製 ID 在 master
的意義和以前運行 ID
仍然是同樣的,但對於 slave
來講,它保存的複製 ID
(即 replid
) 表示當前正在同步的 master
的複製 ID
。master_replid 2
則表示前一個 master
的複製 ID
(若是它以前沒複製過其餘的 master
,那這個字段沒用),這個在主從角色發生改變的時候會用到。
struct redisServer {
...
/* Replication (`master`) */
char replid[CONFIG_RUN_ID_SIZE+1]; /* My current replication ID. */
char replid2[CONFIG_RUN_ID_SIZE+1]; /* replid inherited from `master`*/
}
複製代碼
slave
在乎外關閉前會調用 rdbSaveInfoAuxFields
函數把當前的複製 ID
(即關閉前正在複製的 master
的 replid
,由於 slave
中的 replid
字段保存的是 master
的複製 ID
) 和複製偏移量一塊兒保存到 RDB
文件中,後面該 slave
重啓的時候,就能夠從 RDB
文件中讀取複製 ID
和複製偏移量,而後重連上 master
後 slave
將這兩個值發送給 master
,master
會以下判斷是否容許 psync
:
// 若是 `slave` 發送過來的複製 ID 是當前 `master` 的複製 ID, 說明 `master` 沒變過
if (strcasecmp(master_replid, server.replid) &&
// 或者和如今的新 `master` 曾經屬於同一 `master`
(strcasecmp(master_replid, server.replid2) ||
// 但同步進度不能比當前 `master` 還快
psync_offset > server.second_replid_offset)) {
... ...
}
// 判斷同步進度是否已經超過範圍
if (!server.repl_backlog ||
psync_offset < server.repl_backlog_off ||
psync_offset > (server.repl_backlog_off + server.repl_backlog_histlen)) {
... ...
}
複製代碼
另外當節點從 slave
提高爲 master
後,會保存兩個複製 ID(以前角色是 slave
的時候 replid2 沒用,如今要派上用場了),分別是 replid
和 replid 2
,其餘 slave
複製的時候能夠根據第二個複製 ID
來進行部分重同步。對應上述代碼中第二行判斷的狀況。
這是一個不定時更新的、披着程序員外衣的文青小號,歡迎關注。