前言
參考資料:《Redis設計與實現 第二版》;
第二部分為單機資料庫的實現,主要由以下模組組成:資料庫、持久化、事件、客戶端與伺服器;
本篇將介紹 Redis 中的持久化技術,主要有兩種:RDB持久化和AOF持久化;
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 檔案的替換;
-
只有號處理函式執行時會對伺服器程式(父程式)造成阻塞;