前言
參考資料:《Redis設計與實現 第二版》;
第二部分為單機數據庫的實現,主要由以下模塊組成:數據庫、持久化、事件、客戶端與服務器;
本篇將介紹 Redis 中的持久化技術,主要有兩種:RDB持久化和AOF持久化;
與本章相關的 Redis 命令總結在下篇文章,歡迎點擊收藏,本篇將不再重復:
《Redis常用命令及示例總結(API)》:https://www.cnblogs.com/dlhjw/p/15639773.html
1. RDB 持久化
1.1 RDB 文件的創建與載入
- Redis使用 SAVE 和 BGSAVE 命令生成 RDB 文件;
- SAVE:會阻塞 Redis 服務器進程,直到 RDB 文件創建完畢為止,阻塞期間服務器不能處理任何命令請求;
- BGSAVE:會派生一個子進程,由指進程負責創建 RDB 文件,父進程繼續處理命令請求。BGSAVE 執行期間,會發生以下特殊情況:
- 在 BGSAVE 命令執行期間,客戶端發送 SAVE 和 BGSAVE 命令會被服務器拒絕,防止產生競爭條件。客戶端發送 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; }
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 文件結構的邏輯圖:
-
各個字段含義:
字段 長度 儲存值 說明 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
部分的邏輯結構:
-
各字段含義:
字段 長度 存儲值 說明 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 對象的定義;
- 字符串對象的格式與示例:
- 字符串對象可分為:壓縮字符串和無壓縮字符串兩種:
- 列表與集合對象的格式:
- 哈希表對象的格式:
- 有序集合對象的格式:
-
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 文件即可;
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 重寫期間,服務器進程(父進程)可以繼續處理命令請求;
- 子進程帶有服務器進程的數據副本,使用子進程而不是線程,避免使用鎖的情況下保證數據安全;
-
Redis 服務器設置一個 AOF 重寫緩沖區,以保證:
- AOF 緩沖區的內容會定期被寫入和同步到 AOF 文件,對現有 AOF 文件的處理工作如常進行;
- 從創建子進程開始,服務器執行的所有寫命令都會被記錄到 AOF 重寫緩沖區里;
-
子進程完成 AOF 重寫工作后,向父進程發送一個信號,父進程接到信號后調用信號處理函數,執行以下工作:
- 將 AOF 重寫緩沖區中的所有內容寫入到新 AOF 文件,此時新 AOF 文件保存的數據庫狀態將與服務器當前的數據庫狀態一致;
- 對新的 AOF 文件進行改名,原子地覆蓋現有的 AOF 文件,完成新舊兩個 AOF 文件的替換;
-
只有號處理函數執行時會對服務器進程(父進程)造成阻塞;
最后
