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



前言

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

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

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

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

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


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 文件后台重寫過程


最后

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM