Redis 主從複製 psync1 和 psync2 的區別

YoungChen發表於2019-05-06

寫在前面

在分散式環境中,資料副本 (Replica) 和複製 (Replication) 作為提升系統可用性和讀寫效能的有效手段被大量應用系統設計中,Redis 也不例外。Redis 作為單機資料庫使用時,適用常見有限且存在單點當機問題,無法維持高可用。因此 Redis 允許通過 SLAVEOF 命令或者 slaveof 配置項來讓一個 Redis server 複製另一個 Redis server 的資料集和狀態,我們稱之為主從複製,主伺服器下文稱 master,從伺服器下文稱 slaveRedis 採用非同步的複製機制。

主從複製機制的演變

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 的機制本文不再贅述。

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 來進行部分重同步。對應上述程式碼中第二行判斷的情況。

參考資料

寫在最後

這是一個不定時更新的、披著程式設計師外衣的文青小號,歡迎關注。

Redis 主從複製 psync1 和 psync2 的區別

相關文章