1. 程式人生 > 其它 >Redis | 第11章 伺服器的複製《Redis設計與實現》

Redis | 第11章 伺服器的複製《Redis設計與實現》

目錄

前言

參考資料:《Redis設計與實現 第二版》;

第四部分為多機資料庫的實現,主要由以下模組組成:複製Sentinel叢集

本篇將介紹 Redis 的複製功能。在 Redis 中,使用者可以通過執行 SLAVEOF 命令或者設定 salveof 選項,讓一個從伺服器複製主伺服器。

與本章相關的 Redis 命令總結在下篇文章,歡迎點選收藏,本篇將不再重複:

《Redis常用命令及示例總結(API)》https://www.cnblogs.com/dlhjw/p/15639773.html


1. 舊版複製功能的實現

  • 舊版指 Redis 2.8 以前版本;
  • 舊版 Redis 的複製功能分為同步(sync)和命令傳播(command propagate)兩個操作:
    • 同步操作:將從伺服器的資料庫狀態更新至主伺服器當前所處的資料庫狀態;
    • 命令傳播:用於在主伺服器的資料庫狀態被修改,導致主從伺服器的資料庫狀態出現不一致時,讓主從伺服器重新回到一致狀態;

1.1 同步與命令傳播

  • 同步:客戶端向從伺服器傳送 SLAVEOF 命令,從伺服器執行以下步驟:
    • 1)從伺服器向主伺服器傳送 SYNC 命令;
    • 2)收到 SYNC 命令的主伺服器執行 BGSAVE 命令,在後臺生成 RDB 檔案,並使用一個緩衝區記錄此刻開始執行的所有寫命令;
    • 3)當主伺服器完成 BGSAVE 命令時,將 RDB 檔案發給從伺服器,從伺服器接收並載入 RDB 檔案,將資料庫狀態更新至主伺服器執行 BGSAVE 命令時的資料庫狀態;
    • 4)主伺服器將記錄在緩衝區裡的所有寫命令傳送給從伺服器,從伺服器執行寫命令,將資料庫狀態更新至主伺服器資料庫當前的狀態;
  • 命令傳播:同步操作完成後,主伺服器會將自己執行的寫命令,傳送給從伺服器。從伺服器執行後,主從伺服器資料庫狀態再次回到一致狀態;

1.2 舊版複製功能的缺陷

  • 處於命令傳播階段的主從伺服器因為網路問題中斷了複製,重連後從伺服器會向主伺服器傳送 SYNC 命令執行同步操作;
  • SYNC 命令非常消耗資源:
    • 主伺服器執行 BGSAVE 命令時:耗費主伺服器大量 CPU、記憶體與磁碟 IO 資源;
    • 主伺服器將 RDB 檔案發給從伺服器時:耗費主從伺服器大量網路資源(流量和頻寬),並對主伺服器響應命令請求的時間產生影響;
    • 從伺服器在載入 RDB 檔案期間:從伺服器阻塞無法處理命令請求;

2. 新版複製功能的實現

  • 新版指 Redis 2.8 以後版本;
  • 新版 Redis 的複製功能分為完全重同步(full resynchronization)和部分重同步(partial resynchronization)兩個操作:
    • 完全重同步:與初次複製情況相同;
    • 部分重同步:主伺服器僅將主從伺服器在斷線期間的寫命令發給從伺服器。使用 PSYNC 命令;

2.1 部分重同步的實現原理

  • 部分重同步依賴以下三個部分:
    • 主從伺服器的複製偏移量(replication offset);
    • 主伺服器的複製積壓緩衝區(replication backlog);
    • 主伺服器的執行 ID(run ID);
  • 複製偏移量
    • 執行復制的主從伺服器雙方會分別維護一個複製偏移量;
    • 主伺服器每次向從伺服器傳播 N 個位元組資料時,將自己的複製偏移量加 N;
    • 從伺服器每次收到主伺服器傳播來的 N 位元組資料時,將自己的複製偏移量加 N;
    • 通過對比主從伺服器的複製偏移量可以判斷其資料庫狀態是否一致;
  • 複製積壓緩衝區
    • 複製積壓緩衝區由主伺服器維護一個固定長度(fixed-size)先進先出(FIFO)佇列,預設大小 1MB;
    • 當主伺服器進行命令傳播時,會將命令發給從伺服器,同時將寫命令入隊到複製積壓緩衝區;
    • 重連時,從伺服器將自己的複製偏移量 offset 發給主伺服器:
      • offset+1 的內容在複製積壓緩衝區內,主伺服器對從伺服器執行部分同步操作;
      • 反之,執行完整重同步操作;
  • 複製積壓緩衝區的大小可以根據公式估算:second * write_size_per_second
    • second 為從伺服器斷線後重連所需平均時間;
    • write_size_per_second 為主伺服器平均每秒產生的寫命令資料量;
    • 為安全起見,可將複製積壓緩衝區大小設定為:上述公式乘 2;
  • 伺服器執行 ID
    • 每個 Redis 都有自己的執行 ID;
    • 執行 ID 在伺服器啟動時自動生成,由 40 個隨機的十六進位制字元組成;
    • 從伺服器對主伺服器進行初次複製時,主伺服器會將自己的執行 ID 傳送給從伺服器,從伺服器將這個 ID 儲存;
    • 重連時,從伺服器向主伺服器傳送儲存的執行 ID:
      • 若從伺服器儲存的 ID 為當前主伺服器執行 ID,根據偏移量判斷重同步方式;
      • 反之,執行完整重同步操作;

3. PSYNC 命令的實現

  • PSYNC 命令的呼叫方法有兩種:
    • 從伺服器沒有複製過任何主伺服器,或之前執行過 SLAVEOF NO ONE 命令,那麼從伺服器在開始複製時向主伺服器傳送 PSYNC ? -1 命令,主動請求主伺服器進行完整重同步;
    • 反之,從伺服器傳送 PSYNC master_run_id offset 命令,master_run_id 為上一次複製時的主伺服器的執行 ID,offset 為從伺服器當前的複製偏移量;
  • 主伺服器接受 PSYNC 命令後會產生三種回覆:
    • 完整重同步:返回 +FULLRESYNC master_run_id offset,master_run_id 為當前主伺服器的執行 ID,offset 為主伺服器當前的複製偏移量;
    • 部分重同步:返回 +CONTINUE,從伺服器只需要等待主伺服器傳送自己缺少的部分資料;
    • 錯誤:返回 -ERR,主伺服器版本低於 Redis 2.8,從伺服器傳送 SYNC 命令,與主伺服器執行完整同步操作;

4. 複製的詳細步驟

  • 該步驟為 Redis 2.8 版本以上
  • 客戶端向從伺服器傳送 SLAVEOF ip port 命令;

4.1 設定主伺服器的地址和埠

  • 從伺服器將 SLAVEOF ip port 命令的 ip 地址和 port 埠儲存到伺服器狀態裡:

    struct redisServer{
        //...
        //主伺服器的地址
        char *masterhost;
        //主伺服器的埠
        int masterport;
    };
    
  • 從伺服器向客戶端返回 OK,然後開始複製;

4.2 建立套接字連線

  • 從伺服器根據 masterhostmasterport 建立連向主伺服器的套接字連線;
  • 套接字連線(connect)成功後,從伺服器為該套接字關聯一個檔案事務處理器,專門用來處理複製工作;
  • 主伺服器接受(accept)從伺服器的套接字連線後,為該套接字建立響應客戶端狀態,並將從伺服器視作客戶端(client);
  • 此時,從伺服器具備伺服器(server)和客戶端(client)雙重身份;

4.3 傳送 PING 命令

  • 從伺服器成為主伺服器的客戶端後,向主伺服器傳送 PING 命令;
  • PING 命令的作用:
    • 檢查套接字的讀寫狀態是否正常;
    • 檢查主伺服器能否正常處理命令請求;
  • 主伺服器對 PING 命令的回覆有 3 種情況:
    • 返回命令回覆:但從伺服器不能在有限時間內讀取命令內容,表示主從伺服器間網路連線狀態不佳。此時從伺服器會斷開並重新建立連向主伺服器的套接字;
    • 返回錯誤:表示主伺服器暫時沒法處理從伺服器的命令請求。此時從伺服器會斷開並重新建立連向主伺服器的套接字;
    • 返回 PONG:表示網路連線正常,從伺服器可以繼續執行復制工作;

4.4 身份驗證

  • 從伺服器收到 PONG 回覆後,根據是否設定 masterauth 選項決定是否進行身份驗證;

    • 從伺服器沒有設定 masterauth 選項,不進行身份驗證;
    • 從伺服器設定了 masterauth 選項,需要進行身份驗證;
  • 在需要進行身份驗證的情況下,從伺服器給主伺服器傳送 AUTH password 命令,命令的引數 password 為從伺服器 masterauth 選項的值;

  • 從伺服器在進行身份驗證時根據:主伺服器的 requirepass 選項和從伺服器的 masterauth 選項不同,可能遇到以下情況:

    主伺服器的 requirepass 選項 從伺服器的 masterauth 選項 情況
    設定 設定 相同則繼續複製,不同則返回 invalid password 錯誤
    設定 沒有設定 返回 NOAUTH 錯誤
    沒有設定 設定 返回 no password is set 錯誤
    沒有設定 沒有設定 繼續複製工作

4.5 傳送埠資訊

  • 身份驗證完成後,從伺服器執行 REPLCONF listening-port port-number 命令,向主伺服器傳送自己的監聽埠號 port-number

  • 主伺服器接受後,儲存在從伺服器的客戶端狀態 slave_listening_port 屬性中:

    typedef struct redisClient{
        //...
        // 從伺服器的監聽埠號
        int slave_listening_port;
    } redisClient;
    

4.6 同步

  • 從伺服器向主伺服器傳送 PSYNC 命令,執行同步操作,將自己的資料庫更新至主伺服器資料庫當前所處的狀態;
  • 在同步操作執行完後,主伺服器會成為從伺服器的客戶端,理由如下:
    • 完整重同步情況:需要將緩衝區裡的寫命令傳送給從伺服器;
    • 部分重同步情況:需要將複製積壓緩衝區裡的寫命令傳送給從伺服器;

4.7 命令傳播

  • 完成同步後,主伺服器進入命令傳播階段,一直將寫命令發給從伺服器;

5. 心跳檢測

  • 在命令傳播階段,從伺服器預設每秒向主伺服器傳送命令 REPLCONF ACK replication_offsetreplication_offset 引數是從伺服器當前的複製偏移量;
  • 心跳檢測的三個作用:
    • 檢測主從伺服器的網路連線狀態:使用 INFO replication 命令可以檢視從伺服器最後一次向主伺服器傳送 REPLCONF ACK 命令距離現在過了多久,一般在 0~1 秒為正常;
    • 輔助 min-slaves 配置選項:當從伺服器數量 x 少於 min-slaves-to-write 屬性值或 x 個伺服器的延遲大於等於 min-slaves-max-lag 屬性值時,主伺服器拒絕寫命令;
    • 檢測命令丟失:當主伺服器發現從伺服器的 replication_offset 引數與自己的不一致時,補發寫命令資料;
  • 補發命令資料與部分重同步的區別在於:前者沒有斷線,後再斷線了;
  • Redis 2.8 版本以前沒有補發命令資料功能;


最後

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標註出處!