Redis | 第5章 Redis 中的持久化技術《Redis設計與實現》

多氯環己烷 發表於 2021-11-27
Redis


前言

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

第二部分為單機資料庫的實現,主要由以下模組組成:資料庫持久化事件客戶端伺服器

本篇將介紹 Redis 中的持久化技術,主要有兩種:RDB持久化AOF持久化


1. RDB 持久化

1.1 RDB 檔案的建立與載入

  • Redis使用 SAVEBGSAVE 命令生成 RDB 檔案;
    • SAVE:會阻塞 Redis 伺服器程式,直到 RDB 檔案建立完畢為止,阻塞期間伺服器不能處理任何命令請求;
    • BGSAVE:會派生一個子程式,由指程式負責建立 RDB 檔案,父程式繼續處理命令請求。BGSAVE 執行期間,會發生以下特殊情況:
      • BGSAVE 命令執行期間,客戶端傳送 SAVEBGSAVE 命令會被伺服器拒絕,防止產生競爭條件。客戶端傳送 BGREWRITEAOF 命令會被延遲;
      • BGREWRITEAOF 命令執行期間,客戶端傳送 BGSAVE 命令會被伺服器拒絕;
  • 建立 RDB 檔案由 rdb.c/rdbSave 函式完成;
  • 載入 RDB 檔案由 rdb.c/rdbLoad 函式完成;
  • RDB 檔案的載入工作是在伺服器啟動時自動執行,只要 Redis 伺服器在啟動時檢測到 RDB 檔案存在,就會自動載入 RDB 檔案;
  • AOF 檔案的更新頻率通常比 RDB 檔案更新頻率高:
    • 當伺服器開啟了 AOF 持久化功能時,會優先使用 AOF 檔案還原資料庫狀態;
    • 當伺服器關閉了 AOF 持久化功能時,才會使用 RDB 檔案來還原資料庫狀態;
  • 伺服器在載入 RDB 檔案期間,會一直處於阻塞狀態,直到載入工作完成為止;

1.2 自動間隔性儲存

1.2.1 設定儲存條件

  • 伺服器會根據 save 選項所設定的儲存值,設定伺服器狀態 redisServer結構的 saveparams 屬性;

  • redisServer 的結構定義:

    struct redisServer{
        //...
        //記錄了儲存條件的陣列
        struct saveparam *saveparams;
        //
    }
    
  • saveparams 屬性是一個陣列,每個 saveparam 結構儲存了一個 save 設定的儲存條件;

  • saveparam 的結構定義:

    struct saveparam{
        //秒數
        time_t seconds;
        //修改值
        int changes;
    }
    

saveparam屬性

1.2.2 dirty 計數器和 lastsave 屬性

  • dirty 屬性和 lastsave 屬性在 redisServer 結構體裡:

    struct redisServer{
        //...
        //修改計數器
        long long dirty;
        //上一次執行儲存的時間
        time_t lastsave;
    };
    
    • dirty 計數器記錄距離上一次成功執行 SAVE 命令或者 BGNAME 命令之後,伺服器對資料庫狀態進行了多少次修改;
    • lastsave 屬性是一個 UNIX 時間戳,記錄了伺服器上一次成功執行 SAVE 命令或 BGSAVE 命令的時間;

1.2.3 檢查儲存條件是否滿足

  • Redis 的伺服器週期性操作函式 serverCron 預設每隔 100ms 會執行一次,其中包括檢查 save 選項所設定的儲存條件是否滿足(遍歷並檢查 saveparams 陣列中的所有儲存條件),滿足則執行 BGSAVE 命令;

1.3 RDB 檔案

1.3.1 RDB 的檔案結構

  • RDB 檔案結構的邏輯圖:
    RDB檔案結構

  • 各個欄位含義:

    欄位 長度 儲存值 說明
    REDIS 5位元組 “REDIS” 在載入檔案時,快速檢查所載入的檔案是否為 RDB 檔案
    db_version 4位元組 字串表示的整數 RDB 檔案的版本號
    databases 0個或任意多個資料庫,以及資料庫中的鍵值對資料
    EOP 1位元組 EOP 常量 表示 RDB 檔案正文內容的結束
    check_sum 8位元組 無符號整數 前4個部分的校驗和

1.3.2 database 的檔案結構

  • database 為 RDB 檔案的結構組成部分;
  • databases 部分的邏輯結構:

databases部分的邏輯結構

  • 各欄位含義:

    欄位 長度 儲存值 說明
    SELECTDB 1位元組 常量 表示接下來讀入資料庫號碼
    db_number 1、2或5位元組 數字 表示資料庫號碼
    key_value_pairs 長度不定 資料庫所有的鍵值對資料

1.3.3 key_value_pairs 的檔案結構

  • key_value_pairs 為 databases 的結構組成部分;有兩種型別,一種不帶過期時間,一種帶過期時間;
  • key_value_pairs 部分的邏輯結構:
    不帶過期時間的鍵值對

帶有過期時間的鍵值對

  • 各欄位含義:

    欄位 長度 儲存值 說明
    EXPIRETIME_MS 1位元組 數值 表示過期時間
    ms 8位元組 數值 以毫秒為單位的 UNIX 時間戳
    TYPE 1位元組 常量 代表一種物件型別或底層編碼
    key 長度不定 字串物件 表示鍵物件
    value 長度不定 各種物件 表示值物件

1.3.4 value 的編碼

  • value 為 key_value_pairs 的結構組成成分;
  • value 值物件的結構和長度會根據 TYPE 型別的不同而不同;
  • value可以是字串物件列表物件集合物件雜湊表物件有序集合物件INTSET編碼的集合ZIPLIST編碼的列表、雜湊表或有序集合
  • value的格式與編碼對應請見 《第3章 物件》1.1 物件的定義
  • 字串物件的格式與示例:
  • 字串物件可分為:壓縮字串無壓縮字串兩種:

壓縮字串物件的value格式

字串物件的value示例

  • 列表與集合物件的格式:
    列表與集合物件的value格式

列表與集合物件的value示例

  • 雜湊表物件的格式:

雜湊表物件的value格式
雜湊表物件的value示例

  • 有序集合物件的格式:

有序集合物件的value格式
有序集合物件的value示例

  • INTSET 編碼集合的格式:

    • 將整數集合轉換成字串即可;
  • ZIPLIST編碼的列表、雜湊表或有序集合的格式:

    • 將壓縮列表轉換成一個字串物件,然後再儲存到 RDB 檔案;

1.4 RDB 檔案的示例

  • 不包含任何鍵值對的 RDB 檔案:

    REDIS標識 db_version EOF標識 check_num
    REDIS 0006 377 334 263 c 360 z 334 362 v
  • 包含字串鍵的 RDB 檔案:

    REDIS標識 db_version SELECTDB db_number,0 號資料庫 TYPE,\0 表示字串 key value EOF標識 check_num
    REDIS 0006 376 \0 \0 003 MSG 005 HELLO 377 207 z = 304 f T L 343
  • 包含帶有過期時間的字串鍵的 RDB 檔案:

    REDIS標識 db_version SELECTDB 切換資料庫 EXPIRETIME_MS ms TYPE,\0 表示字串 key value EOF標識 check_num
    REDIS 0006 376 \0 374 \ 2 365 336 @ 001 \0 \0 \0 003 MSG 005 HELLO 377 212 231 x 247 252 } 021 306
  • 包含一個集合鍵的 RDB 檔案:

    REDIS標識 db_version SELECTDB 切換資料庫 常量 REDIS_RDB_TYPE_SET key 集合大小 第一個元素 第二個元素 第三個元素 EOF常量 check_num
    REDIS 0006 376 \0 002 004 LANG 003 004 RUBY 004 JAVA 001 C 377 202 312 r 352 346 305 * 023

2 AOF 持久化與 RDB 持久化的區別

  • AOF 持久化:儲存 Redis 伺服器所執行的命令來記錄資料庫狀態;
  • RDB 持久化:儲存資料庫中的鍵值對來記錄資料庫狀態不同;

3. AOF 持久化

3.1 AOF 持久化的實現

  • AOF 持久化功能可分為:追加(append)、檔案寫入、檔案同步(sync)三個步驟;

  • AOF 檔案中的所有命令都以 Redis 命令請求協議的格式儲存;

  • 當 AOF 持久化功能開啟時,伺服器在執行完一個寫命令之後,會以協議格式將被執行的寫命令追加到伺服器狀態的 aof_buf 緩衝區的末尾:

    struct redisServer{
        //...
        //AOF 緩衝區
        sds aof_buf;
    };
    
  • AOF 檔案的寫入與同步依賴事件迴圈 loop,每次迴圈主要有三個工作:

    • 處理檔案事件:負責接收客戶端的命令請求,以及向客戶端傳送命令回覆;
    • 處理時間事件:執行需要定時執行的函式;
    • flushAppendOnlyFile():考慮是否將 aof_buf 中的內容追加到 AOF 檔案中;
  • flushAppendOnlyFile() 函式的行為由伺服器配置的 appendfsync 選項的值決定,該值有三種不同的行為:

    appendfsync 選項的值 flushAppendOnlyFile 函式的行為 效率與安全性
    always 將 aof_buf 緩衝區中的所有內容寫入並同步到 AOF 檔案 效率最慢,安全性最高
    everysec 將 aof_buf 緩衝區中的所有內容寫入並同步到 AOF 檔案,如果上次同步 AOF 檔案的事件距離現在超過 1s ,則對再次 AOF 檔案進行同步,並且這個同步由一個執行緒專門負責 效率高
    no 將 aof_buf 緩衝區中的所有內容寫入到 AOF 檔案,但不對 AOF 檔案進行同步,何時同步由作業系統決定 效率最高,安全性最低

3.2 AOF 檔案的載入與資料還原

  • 伺服器建立一個不帶網路連線的偽客戶(fake client),偽客戶端讀入並執行 AOF 檔案即可;

AOF 檔案的載入與資料還原


3.3 AOF 重寫

  • AOF 重寫不需要對現有 AOF 檔案進行任何讀取、分析或寫入操作,而是通過讀取伺服器當前資料庫狀態實現;
  • AOF 重寫功能的實現原理:從資料庫讀取鍵現在的值,然後用一條命令記錄鍵值對,代替之前記錄這個鍵值對的多條命令
  • 為了避免執行命令時造成客戶端輸入緩衝區溢位,重寫程式在處理列表、雜湊表、集合、有序集合這四種鍵時,會檢查元素數量,超過一定數量(64)時會使用多條命令記錄這個鍵的情況;
    • 這個數量由常量 redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD 確定;

3.4 AOF 後臺重寫

  • AOF 重寫需要解決2個問題:
    • 重寫不能阻礙伺服器處理客戶端請求:使用子程式解決;
    • 子程式在 AOF 重寫期間,父程式伺服器對資料庫狀態進行修改,會使伺服器當前狀態與重寫後 AOF 狀態不一致:設定AOF 重寫緩衝區解決;
  • Redis 將 AOF 重寫程式放到子程式裡執行:
    • 子程式進行 AOF 重寫期間,伺服器程式(父程式)可以繼續處理命令請求;
    • 子程式帶有伺服器程式的資料副本,使用子程式而不是執行緒,避免使用鎖的情況下保證資料安全;

AOF 檔案重寫時的伺服器程式與子程式

  • Redis 伺服器設定一個 AOF 重寫緩衝區,以保證:

    • AOF 緩衝區的內容會定期被寫入和同步到 AOF 檔案,對現有 AOF 檔案的處理工作如常進行;
    • 從建立子程式開始,伺服器執行的所有寫命令都會被記錄到 AOF 重寫緩衝區裡;
  • 子程式完成 AOF 重寫工作後,向父程式傳送一個訊號,父程式接到訊號後呼叫訊號處理函式,執行以下工作:

    • 將 AOF 重寫緩衝區中的所有內容寫入到新 AOF 檔案,此時新 AOF 檔案儲存的資料庫狀態將與伺服器當前的資料庫狀態一致;
    • 對新的 AOF 檔案進行改名,原子地覆蓋現有的 AOF 檔案,完成新舊兩個 AOF 檔案的替換;
  • 只有號處理函式執行時會對伺服器程式(父程式)造成阻塞;

AOF 檔案後臺重寫過程


最後

新人制作,如有錯誤,歡迎指出,感激不盡!
歡迎關注公眾號,會分享一些更日常的東西!
如需轉載,請標註出處!
Redis | 第5章 Redis 中的持久化技術《Redis設計與實現》