Redis 主從複製 psync1 和 psync2 的區別

寫在前面

在分佈式環境中,數據副本 (Replica) 和複製 (Replication) 做爲提高系統可用性和讀寫性能的有效手段被大量應用系統設計中,Redis 也不例外。Redis 做爲單機數據庫使用時,適用常見有限且存在單點宕機問題,沒法維持高可用。所以 Redis 容許經過 SLAVEOF 命令或者 slaveof 配置項來讓一個 Redis server 複製另外一個 Redis server 的數據集和狀態,咱們稱之爲主從複製,主服務器下文稱 master,從服務器下文稱 slaveRedis 採用異步的複製機制。html

主從複製機制的演變

Redis 2.64.0 開發人員對複製流程進行逐步的優化,如下是演進過程:程序員

  • 2.8 版本以前 Redis 複製採用 sync 命令,不管是第一次主從複製仍是斷線重連後再進行復制都採用全量同步,成本高
  • 2.8 ~ 4.0 之間複製採用 psync 命令,這一特性主要添加了 Redis 在斷線重連時候可經過 offset 信息使用部分同步
  • 4.0 版本以後也採用 psync,相比於 2.8 版本的 psync 優化了增量複製,這裏咱們稱爲 psync22.8 版本的 psync 能夠稱爲 psync1

咱們先介紹 psync1 的實現,再介紹 psync2 的優化點,至於舊版 sync 的機制本文再也不贅述。redis

psync1

爲了解決舊版 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 命令發給 mastermaster 檢查本身的複製積壓緩衝區,若是發現這部分未同步的命令還在本身的複製積壓緩衝區中的話就能夠利用這些保存的命令進行部分同步,反之若是斷線過久這部分命令已經不在複製緩衝區中了,那沒辦法只能進行全量同步。分佈式

運行 ID

使人疑惑的是上述邏輯看似已經很圓滿了,這個 run_id 是作什麼用呢?其實這是由於 master 可能會在 slave 斷線期間發生變動,例如可能超時失去聯繫或者宕機致使斷線重連的是一個嶄新的 master,再也不是斷線前複製的那個了。天然嶄新的 master 沒有以前維護的複製積壓緩衝區,只能進行全量同步。所以每一個 Redis server 都會有本身的運行 ID,由 40 個隨機的十六進制字符組成。當 slave 初次複製 master 時,master 會將本身的運行 ID 發給 slave 進行保存,這樣 slave函數

重連時再將這個運行 ID 發送給重連上的 mastermaster 會接受這個 ID 並於自身的運行 ID 比較進而判斷是不是同一個 master性能

psync1 流程

若是 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 的運行 IDslave 會將這個 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

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 的複製 IDmaster_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(即關閉前正在複製的 masterreplid,由於 slave 中的 replid 字段保存的是 master 的複製 ID) 和複製偏移量一塊兒保存到 RDB 文件中,後面該 slave 重啓的時候,就能夠從 RDB 文件中讀取複製 ID 和複製偏移量,而後重連上 masterslave 將這兩個值發送給 mastermaster 會以下判斷是否容許 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 沒用,如今要派上用場了),分別是 replidreplid 2,其餘 slave 複製的時候能夠根據第二個複製 ID 來進行部分重同步。對應上述代碼中第二行判斷的狀況。

參考資料

寫在最後

這是一個不定時更新的、披着程序員外衣的文青小號,歡迎關注。

相關文章
相關標籤/搜索