目錄
-
前言
-
1. Redis 資料庫結構
-
2. RDB 持久化
-
2.1. RDB 的創建和載入
-
2.1.1. 手動觸發保存
-
SAVE 命令
-
BGSAVE 命令
-
SAVE 和 BGSAVE 的比較
-
-
2.1.2. 自動觸發保存
-
2.1.3. 啟動自動載入
-
-
2.2. RDB 的檔案結構
-
2.2.1. 存盤路徑
-
2.2.2. 檔案格式
-
database
-
key_value_pairs
-
-
-
2.3. RDB 常用的配置項
-
-
3. AOF 持久化
-
3.1. AOF 的創建和載入
-
3.1.1. AOF 的創建
-
3.1.2. AOF 的載入
-
-
3.2. AOF 的執行流程
-
3.2.1. 命令追加
-
3.2.2. 檔案寫入和檔案同步
-
write()
-
fsync()
-
-
3.2.3. 檔案重寫
-
觸發機制
-
重寫流程
-
壓碩訓制
-
-
-
3.3. AOF 常用的配置項
-
-
4. 資料恢復機制
-
5. RDB 和 AOF 對比
-
5.1. RDB 的優缺點
-
優點
-
缺點
-
-
5.2. AOF 的優缺點
-
優點
-
缺點
-
-
-
6. RDB-AOF 混合持久化
-
7. 持久化策略選擇
-
7.1. RDB 和 AOF 性能開銷
-
7.2. 持久化策略
-
資料庫快取
-
單機環境
-
主從部署
-
異地備災
-
-
-
小結
前言
Redis 是記憶體資料庫,資料都是存盤在記憶體中,為了避免行程退出導致資料的永久丟失,需要定期將 Redis 中的資料以資料或命令的形式從記憶體保存到本地磁盤,當下次 Redis 重啟時,利用持久化檔案進行資料恢復,Redis 提供了 RDB 和 AOF 兩種持久化機制,前者將當前的資料保存到磁盤,后者則是將每次執行的寫命令保存到磁盤(類似于 MySQL 的 Binlog),本文將詳細介紹 RDB 和 AOF 兩種持久化方案,包括操作方法和持久化的實作原理,

正文
Redis 是一個基于鍵值對(K-V)存盤的資料庫服務器,下面先介紹 Redis 資料庫的內部構造以及 K-V 的存盤形式,有助于我們更容易理解 Redis 的持久化機制,
1. Redis 資料庫結構
一個單機的 Redis 服務器默認情況下有 16 個資料庫(0-15 號),資料庫的個數是可配置的,Redis 默認使用的是 0 號資料庫,可以使用 SELECT 命令切換資料庫,
Redis 中的每個資料庫都由一個 redis.h/redisDb 結構表示,它記錄了單個 Redis 資料庫的鍵空間、所有鍵的過期時間、處于阻塞狀態和就緒狀態的鍵、資料庫編號等等,
typedef struct redisDb {
// 資料庫鍵空間,保存著資料庫中的所有鍵值對
dict *dict;
// 鍵的過期時間,字典的鍵為鍵,字典的值為過期事件 UNIX 時間戳
dict *expires;
// 正處于阻塞狀態的鍵
dict *blocking_keys;
// 可以解除阻塞的鍵
dict *ready_keys;
// 正在被 WATCH 命令監視的鍵
dict *watched_keys;
struct evictionPoolEntry *eviction_pool;
// 資料庫編號
int id;
// 資料庫的鍵的平均 TTL,統計資訊
long long avg_ttl;
} redisDb;
由于 Redis 是一個鍵值對資料庫(key-value pairs database), 所以它的資料庫本身也是一個字典,對應的結構正是 redisDb,其中,dict 指向的是一個記錄鍵值對資料的字典,它的鍵是一個字串物件,它的值則可以是字串、串列、哈希表、集合和有序集合在內的任意一種 Redis 型別物件,expires 指向的是一個用于記錄鍵的過期時間的字典,它的鍵為 dict 中的資料庫鍵,它的值為這個資料庫鍵的過期時間戳,這個值以 long long 型別表示,

2. RDB 持久化
RDB 持久化(也稱作快照持久化)是指將記憶體中的資料生成快照保存到磁盤里面,保存的檔案后綴是 .rdb,rdb 檔案是一個經過壓縮的二進制檔案,當 Redis 重新啟動時,可以讀取 rdb 快照檔案恢復資料,RDB 功能最核心的是 rdbSave 和 rdbLoad 兩個函式, 前者用于生成 RDB 檔案并保存到磁盤,而后者則用于將 RDB 檔案中的資料重新載入到記憶體中:

RDB 檔案是一個單檔案的全量資料,很適合資料的容災備份與恢復,通過 RDB 檔案恢復資料庫耗時較短,通常 1G 的快照檔案載入記憶體只需 20s 左右,Redis 提供了手動觸發保存、自動保存間隔兩種 RDB 檔案的生成方式,下面先介紹 RDB 的創建和載入程序,
2.1. RDB 的創建和載入
Redis 服務器默認是通過 RDB 方式完成持久化的,對應 redis.conf 檔案的配置項如下:
# RDB檔案的名稱
dbfilename dump.rdb
# 備份RDB和AOF檔案存放路徑
dir /usr/local/var/db/redis/
2.1.1. 手動觸發保存
Redis 提供了兩個用于生成 RDB 檔案的命令,一個是 SAVE,另一個是 BGSAVE,而觸發 Redis 進行 RDB 備份的方式有兩種,一種是通過 SAVE 命令、BGSAVE 命令手動觸發快照生成的方式,另一種是配置保存時間和寫入次數,由 Redis 根據條件自動觸發保存操作,
1. SAVE 命令
SAVE 是一個同步式的命令,它會阻塞 Redis 服務器行程,直到 RDB 檔案創建完成為止,在服務器行程阻塞期間,服務器不能處理任何其他命令請求,
- 客戶端命令
127.0.0.1:6379> SAVE
OK
- 服務端日志
6266:M 15 Sep 2019 08:31:01.258 * DB saved on disk
執行 SAVE 命令后,Redis 在服務端行程(PID 為 6266)執行了 SAVE 操作,這個操作發生期間會一直阻塞 Redis 客戶端的請求處理,
2. BGSAVE 命令
BGSAVE 是一個異步式的命令,和 SAVE 命令直接阻塞服務器行程的做法不同,BGSAVE 命令會派生出一個子行程,由子行程負責創建 RDB 檔案,服務器行程(父行程)繼續處理客戶的命令,
- 客戶端命令
127.0.0.1:6379> BGSAVE
Background saving started
- 服務端日志
6266:M 15 Sep 2019 08:31:22.914 * Background saving started by pid 6283
6283:C 15 Sep 2019 08:31:22.915 * DB saved on disk
6266:M 15 Sep 2019 08:31:22.934 * Background saving terminated with success
通過服務端輸出的日志,可以發現 Redis 在服務端行程(PID 為 6266)會為 BGSAVE 命令單獨創建(fork)一個子行程(PID 為 6283),并由子行程在后臺完成 RDB 的保存程序,在操作完成之后通知父行程然后退出,在整個程序中,服務器行程只會消耗少量時間在創建子行程和處理子行程信號量上面,其余時間都是待命狀態,
BGSAVE 是觸發 RDB 持久化的主流方式,下面給出 BGSAVE 命令生成快照的流程:

- 客戶端發起 BGSAVE 命令,Redis 主行程判斷當前是否存在正在執行備份的子行程,如果存在則直接回傳
- 父行程 fork 一個子行程 (fork 的程序中會造成阻塞的情況),這個程序可以使用 info stats 命令查看 latest_fork_usec 選項,查看最近一次 fork 操作消耗的時間,單位是微秒
- 父行程 fork 完成之后,則會回傳 Background saving started 的資訊提示,此時 fork 阻塞解除
- fork 創建的子行程開始根據父行程的記憶體資料生成臨時的快照檔案,然后替換原檔案
- 子行程備份完畢后向父行程發送完成資訊,父行程更新統計資訊
3. SAVE 和 BGSAVE 的比較
| 命令 | SAVE | BGSAVE |
|---|---|---|
| IO 型別 | 同步 | 異步 |
| 是否阻塞 | 全程阻塞 | fork 時發生阻塞 |
| 復雜度 | O(n) | O(n) |
| 優點 | 不會消耗額外的記憶體 | 不阻塞客戶端 |
| 缺點 | 阻塞客戶端 | fork 子行程消耗記憶體 |
2.1.2. 自動觸發保存
因為 BGSAVE 命令可以在不阻塞服務器行程的情況下執行,所以 Redis 的組態檔 redis.conf 提供了一個 save 選項,讓服務器每隔一段時間自動執行一次 BGSAVE 命令,用戶可以通過 save 選項設定多個保存條件,只要其中任意一個條件被滿足,服務器就會執行 BGSAVE 命令,Redis 組態檔 redis.conf 默認配置了以下 3 個保存條件:
save 900 1
save 300 10
save 60 10000
那么只要滿足以下 3 個條件中的任意一個,BGSAVE 命令就會被自動執行:
- 服務器在 900 秒之內,對資料庫進行了至少 1 次修改,
- 服務器在 300 秒之內,對資料庫進行了至少 10 次修改,
- 服務器在 60 秒之內,對資料庫進行了至少 10000 次修改,
比如通過命令 SET msg "hello" 插入一條鍵值對,等待 900 秒后 Reids 服務器行程自動觸發保存,輸出如下:
6266:M 15 Sep 2019 08:46:22.981 * 1 changes in 900 seconds. Saving...
6266:M 15 Sep 2019 08:46:22.986 * Background saving started by pid 6266
6476:C 15 Sep 2019 08:46:23.015 * DB saved on disk
6266:M 15 Sep 2019 08:46:23.096 * Background saving terminated with success
Redis 服務器會周期性地操作 serverCron 函式,這個函式每隔 100 毫秒就會執行一次,它的一項任務就是檢查 save 選項所設定的保存條件是否滿足,如果滿足的話,就自動執行 BGSAVE 命令,
2.1.3. 啟動自動載入
和使用 SAVE 和 BGSAVE 命令創建 RDB 檔案不同,Redis 沒有專門提供用于載入 RDB 檔案的命令,RDB 檔案的載入程序是在 Redis 服務器啟動時自動完成的,啟動時只要在指定目錄檢測到 RDB 檔案的存在,Redis 就會通過 rdbLoad 函式自動載入 RDB 檔案,
下面是 Redis 服務器啟動時列印的日志,倒數第 2 條日志是在成功載入 RDB 檔案后列印的,
$ redis-server /usr/local/etc/redis.conf
6266:M 15 Sep 2019 08:30:41.832 # Server initialized
6266:M 15 Sep 2019 08:30:41.833 * DB loaded from disk: 0.001 seconds
6266:M 15 Sep 2019 08:30:41.833 * Ready to accept connections
由于 AOF 檔案屬于增量的寫入命令備份,RDB 檔案屬于全量的資料備份,所以更新頻率比 RDB 檔案的更新頻率高,所以如果 Redis 服務器開啟了 AOF 持久化功能,那么服務器會優先使用 AOF 檔案來還原資料庫狀態;只有在 AOF 的持久化功能處于關閉狀態時,服務器才會使用使用 RDB 檔案還原資料庫狀態,

2.2. RDB 的檔案結構
RDB 檔案是經過壓縮的二進制檔案,下面介紹關于 RDB 檔案內部構造的一些細節,
2.2.1. 存盤路徑
SAVE 命令和 BGSAVE 命令都只會備份當前資料庫,備份檔案名默認為 dump.rdb,可通過組態檔修改備份檔案名 dbfilename xxx.rdb,可以通過以下命令查看備份檔案目錄和 RDB 檔案名稱:
$ redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> CONFIG GET dir
1) "dir"
2) "/usr/local/var/db/redis"
127.0.0.1:6379> CONFIG GET dbfilename
1) "dbfilename"
2) "dump.rdb"
RDB 檔案的存盤路徑既可以在啟動前配置,也可以通過命令動態設定,
- 配置項:通過 dir 配置指定目錄,dbfilename 指定檔案名
- 動態指定:Redis 啟動后也可以動態修改 RDB 存盤路徑,在磁盤損害或空間不足時非常有用,執行命令為:
CONFIG SET dir $newdir
CONFIG SET dbfilename $newFileName
2.2.2. 檔案格式
RDB 檔案有固定的格式要求,它保存的是二進制資料,大體可以分為以下 5 部分:
-
REDIS:檔案頭保存的是長為 5 個位元組的 REDIS 字符,用于標識當前檔案為 RDB 型別
-
db_version:一個 4 個位元組長的整數字串,用于記錄 RDB 檔案的版本號
-
aux:記錄著 RDB 檔案中元資料資訊,包含 8 個附加
-
- redis-ver:Redis 實體的版本號
- redis-bits:運行 Redis 實體的主機架構,64 位或 32 位
- ctime:RDB 創建時的 Unix 時間戳
- used_mem:存盤快照時使用的記憶體大小
- repl-stream-db:Redis 服務器的 db 的索引
- repl-id:Redis 主實體的 ID(replication id)
- repl-offset:Redis 主實體的偏稱量(replication offset)
- aof-preamble:是否在 AOF 檔案頭部放置 RDB 快照(即開啟混合持久化)
-
databases:部分包含著零個或者任意多個資料庫,以及各個資料庫的鍵值對資料
-
EOF:是 1 個位元組的常量,用于標志 RDB 檔案的正文內容結束
-
check_sum:一個 8 位元組長的整數,保存著由前面四個部分計算得到的校驗和,用于檢測 RDB 檔案的完整性

1. database
一個 RDB 檔案的 databases 部分包含著零個或者任意多個資料庫(database),而每個非空的 database 都包含 SELECTDB、db_number 以及 key_value_pairs 三個部分:
- SELECTDB:長度為一個位元組的常量,告訴用戶程式接下來要讀取的是一個 db_number
- db_number:保存著一個資料庫編號,當程式讀到 db_number 時,服務器會立即呼叫 SELECT 命令切換到對應編號的資料庫
- key_value_pairs:保存了資料庫中的所有鍵值對資料,包括帶過期時間和不帶過期時間兩種型別的鍵值對
2. key_value_pairs
RDB 的 key_value_pairs 部分保存了一個或者多個鍵值對,如果鍵值對有過期時間,過期時間會被保存在鍵值對的前面,下面是這兩種鍵值對的內部結構:

-
EXPIREMENT_MS:長度為一個位元組的常量,告訴用戶程式接下來要讀取的是一個以毫秒為單位的過期時間
-
ms:一個長度為 8 個位元組的整數,記錄著鍵值對的過期時間,是一個以毫秒為單位的時間戳
-
TYPE:記錄了 value 的型別,長度為 1 個位元組,每個 TYPE 常量都代表了一種物件型別或者底層編碼, 當服務器讀入 RDB 檔案中的鍵值對資料時, 程式會根據 TYPE 的值來決定如何讀入和解釋 value 的資料,它的值定義通常為以下常量之一:
-
- REDIS_RDB_TYPE_STRING:字串
- REDIS_RDB_TYPE_LIST:串列型別
- REDIS_RDB_TYPE_SET:集合型別
- REDIS_RDB_TYPE_ZSET:有序集合
- REDIS_RDB_TYPE_HASH:哈希型別
- REDIS_RDB_TYPE_LIST_ZIPLIST:串列型別
- REDIS_RDB_TYPE_SET_INT_SET:集合型別
- REDIS_RDB_TYPE_ZSET_ZIPLIST:有序集合
- REDIS_RDB_TYPE_HASH_ZIPLIST:哈希型別
-
key:一個字串物件,編碼格式和 REDIS_RDB_TYPE_STRING 型別的 value 一樣
-
value:取決于 TYPE 的型別,物件型別可以是 string、list、set、zset 和 hash
為了查看 RDB 檔案內部的結構,執行以下命令往 Redis 服務器插入 3 條鍵值對資料:
127.0.0.1:6379> SADD fruits "apple" "banana" "orange"
(integer) 3
127.0.0.1:6379> LPUSH numbers 128 256 512
(integer) 3
127.0.0.1:6379> SET msg "hello"
OK
執行 SAVE 操作,將 Redis 行程中的資料強制持久化到 dump.rdb 檔案中
127.0.0.1:6379> SAVE
OK
通過 Linux 的 od 命令將二進制檔案 dump.rdb 中的資料轉換為 ASCII 格式輸出,跟前面提到的存盤格式大致是一樣的:
$ od -c dump.rdb
0000000 R E D I S 0 0 0 9 372 \t r e d i s
0000020 - v e r 005 5 . 0 . 5 372 \n r e d i
0000040 s - b i t s 300 @ 372 005 c t i m e 200
0000060 200 200 231 ] 372 \b u s e d - m e m 302 200
0000100 \v 020 \0 372 \f a o f - p r e a m b l
0000120 e 300 \0 376 \0 373 003 \0 \0 003 m s g 005 h e
0000140 l l o 016 \a n u m b e r s 001 027 027 \0
0000160 \0 \0 022 \0 \0 \0 003 \0 \0 300 \0 002 004 300 \0 001
0000200 004 300 200 \0 377 002 006 f r u i t s 003 006 o
0000220 r a n g e 005 a p p l e 006 b a n a
0000240 n a 377 214 ? ** 3 366 < r X
0000253
2.3. RDB 常用的配置項
下面是 redis.conf 檔案中和 RDB 檔案相關的常用配置項(以及默認值):
- save m n:bgsave 自動觸發的條件;如果沒有 save m n 配置,相當于自動的 RDB 持久化關閉,不過此時仍可以通過其他方式觸發,
- stop-writes-on-bgsave-error yes:當 bgsave 出現錯誤時,Redis 是否停止執行寫命令,如果設定為 yes,則當硬碟出現問題時,可以及時發現,避免資料的大量丟失;如果設定為 no,則 Redis 忽略 bgsave 的錯誤繼續執行寫命令,當對 Redis 服務器的系統(尤其是硬碟)使用了監控時,該選項考慮設定為 no,
- rdbcompression yes:是否開啟 RDB 檔案壓縮,
- rdbchecksum yes:是否開啟 RDB 檔案的校驗,在寫入檔案和讀取檔案時都起作用,關閉 checksum 在寫入檔案和啟動檔案時大約能帶來 10% 的性能提升,但是資料損壞時無法發現,
- dbfilename dump.rdb:設定 RDB 的檔案名,
- dir ./:設定 RDB 檔案和 AOF 檔案所在目錄,
3. AOF 持久化
RDB 持久化是定期把記憶體中的資料全量寫入到檔案中,除此之外,RDB 還提供了基于 AOF(Append Only File)的持久化功能,AOF 會把 Redis 服務器每次執行的寫命令記錄到一個日志檔案中,當服務器重啟時再次執行 AOF 檔案中的命令來恢復資料,

AOF 的主要作用是解決了資料持久化的實時性,目前已經成為了 Redis 持久化的主流方式,
3.1. AOF 的創建和載入
默認情況下 AOF 功能是關閉的,Redis 只會通過 RDB 完成資料持久化的,開啟 AOF 功能需要 redis.conf 檔案中將 appendonly 配置項修改為 yes,這樣在開啟 AOF 持久化功能的同時,將基于 RDB 的快照持久化置于低優先級,修改 redis.conf 如下:
# 此選項為AOF功能的開關,默認為no,通過yes來開啟aof功能
appendonly yes
# 指定AOF檔案名稱
appendfilename appendonly.aof
# 備份RDB和AOF檔案存放路徑
dir /usr/local/var/db/redis/
3.1.1. AOF 的創建
重啟 Redis 服務器行程以后,dir 目錄下會生成一個 appendonly.aof 檔案,由于此時服務器未執行任何寫指令,因此 AOF 檔案是空的,執行以下命令寫入幾條測驗資料:
127.0.0.1:6379> SADD fruits "apple" "banana" "orange"
(integer) 3
127.0.0.1:6379> LPUSH numbers 128 256 512
(integer) 3
127.0.0.1:6379> SET msg "hello"
OK
AOF 檔案是純文本格式的,上述寫命令按順序被寫入了 appendonly.aof 檔案(省掉換行符 '\r\n'):
/usr/local/var/db/redis$ cat appendonly.aof
*2 $6 SELECT $1 0
*5 $4 SADD $6 fruits $5 apple $6 banana $6 orange
*5 $5 LPUSH $7 numbers $3 128 $3 256 $3 512
*3 $3 SET $3 msg $5 hello
RDB 持久化的方式是將 apple、banana、orange 的鍵值對資料保存為 RDB 的二進制檔案,而 AOF 是通過把 Redis 服務器執行的 SADD、LPUSH、SET 等命令保存到 AOF 的文本檔案中,下圖是 AOF 檔案內部的構造圖:

3.1.2. AOF 的載入
再次重啟 Redis 服務器行程,觀察啟動日志會發現 Redis 會通過 AOF 檔案加載資料:
52580:M 15 Sep 2019 16:09:47.015 # Server initialized
52580:M 15 Sep 2019 16:09:47.015 * DB loaded from append only file: 0.001 seconds
52580:M 15 Sep 2019 16:09:47.015 * Ready to accept connections
通過命令讀取 AOF 檔案還原的鍵值對資料:
127.0.0.1:6379> SMEMBERS fruits
1) "apple"
2) "orange"
3) "banana"
127.0.0.1:6379> LRANGE numbers 0 -1
1) "512"
2) "256"
3) "128"
127.0.0.1:6379> GET msg
"hello"
3.2. AOF 的執行流程
AOF 不需要設定任何觸發條件,對 Redis 服務器的所有寫命令都會自動記錄到 AOF 檔案中,下面介紹 AOF 持久化的執行流程,

AOF 檔案的寫入流程可以分為以下 3 個步驟:
- 命令追加(append):將 Redis 執行的寫命令追加到 AOF 的緩沖區 aof_buf
- 檔案寫入(write)和檔案同步(fsync):AOF 根據對應的策略將 aof_buf 的資料同步到硬碟
- 檔案重寫(rewrite):定期對 AOF 進行重寫,從而實作對寫命令的壓縮,
3.2.1. 命令追加
Redis 使用單執行緒處理客戶端命令,為了避免每次有寫命令就直接寫入磁盤,導致磁盤 IO 成為 Redis 的性能瓶頸,Redis 會先把執行的寫命令追加(append)到一個 aof_buf 緩沖區,而不是直接寫入檔案,
命令追加的格式是 Redis 命令請求的協議格式,它是一種純文本格式,具有兼容性好、可讀性強、容易處理、操作簡單避免二次開銷等優點,在 AOF 檔案中,除了用于指定資料庫的 select 命令(比如:select 0 為選中 0 號資料庫)是由 Redis 添加的,其他都是客戶端發送來的寫命令,
3.2.2. 檔案寫入和檔案同步
Redis 提供了多種 AOF 快取區的檔案同步策略,相關策略涉及到作業系統的 write() 函式和 fsync() 函式,說明如下:
1. write()
為了提高檔案的寫入效率,當用戶呼叫 write 函式將資料寫入檔案時,作業系統會先把資料寫入到一個記憶體緩沖區里,當緩沖區被填滿或超過了指定時限后,才真正將緩沖區的資料寫入到磁盤里,
2. fsync()
雖然作業系統底層對 write() 函式進行了優化 ,但也帶來了安全問題,如果宕機記憶體緩沖區中的資料會丟失,因此系統同時提供了同步函式 fsync() ,強制作業系統立刻將緩沖區中的資料寫入到磁盤中,從而保證了資料持久化,
Redis 提供了 appendfsync 配置項來控制 AOF 快取區的檔案同步策略,appendfsync 可配置以下三種策略:
- appendfsync always:每執行一次命令保存一次
命令寫入 aof_buf 緩沖區后立即呼叫系統 fsync 函式同步到 AOF 檔案,fsync 操作完成后執行緒回傳,整個程序是阻塞的,這種情況下,每次有寫命令都要同步到 AOF 檔案,硬碟 IO 成為性能瓶頸,Redis 只能支持大約幾百 TPS 寫入,嚴重降低了 Redis 的性能,
- appendfsync no:不保存
命令寫入 aof_buf 緩沖區后呼叫系統 write 操作,不對 AOF 檔案做 fsync 同步;同步由作業系統負責,通常同步周期為 30 秒,這種情況下,檔案同步的時間不可控,且緩沖區中堆積的資料會很多,資料安全性無法保證,
- appendfsync everysec:每秒鐘保存一次
命令寫入 aof_buf 緩沖區后呼叫系統 write 操作,write 完成后執行緒立刻回傳,fsync 同步檔案操作由單獨的行程每秒呼叫一次,everysec 是前述兩種策略的折中,是性能和資料安全性的平衡,因此也是 Redis 的默認配置,也是比較推崇的配置選項,
| 檔案同步策略 | write 阻塞 | fsync 阻塞 | 宕機時的資料丟失量 |
|---|---|---|---|
| always | 阻塞 | 阻塞 | 最多只丟失一個命令的資料 |
| no | 阻塞 | 不阻塞 | 作業系統最后一次對 AOF 檔案 fsync 后的資料 |
| everysec | 阻塞 | 不阻塞 | 一般不超過 1 秒鐘的資料 |
3.2.3. 檔案重寫
隨著命令不斷寫入 AOF,檔案會越來越大,導致檔案占用空間變大,資料恢復時間變長,為了解決這個問題,Redis 引入了重寫機制來對 AOF 檔案中的寫命令進行合并,進一步壓縮檔案體積,
AOF 檔案重寫指的是把 Redis 行程內的資料轉化為寫命令,同步到新的 AOF 檔案中,然后使用新的 AOF 檔案覆寫舊的 AOF 檔案,這個程序不對舊的 AOF 檔案的進行任何讀寫操作,
1. 觸發機制
AOF 重寫程序提供了手動觸發和自動觸發兩種機制:
-
手動觸發:直接呼叫 bgrewriteaof 命令,該命令的執行與 bgsave 有些類似,都是 fork 子行程進行具體的作業,且都只有在 fork 時會阻塞
-
自動觸發:根據 auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage 配置項,以及 aof_current_size 和 aof_base_size 的狀態確定觸發時機
-
- auto-aof-rewrite-min-size:執行 AOF 重寫時,檔案的最小體積,默認值為 64MB
- auto-aof-rewrite-percentage:執行 AOF 重寫時,當前 AOF 大小(aof_current_size)和上一次重寫時 AOF 大小(aof_base_size)的比值
2. 重寫流程
下面以手動觸發 AOF 重寫為例,當 bgrewriteaof 命令被執行時,AOF 檔案重寫的流程如下:

-
客戶端通過 bgrewriteaof 命令對 Redis 主行程發起 AOF 重寫請求
-
當前不存在正在執行 bgsave/bgrewriteaof 的子行程時,Redis 主行程通過 fork 操作創建子行程,這個程序主行程是阻塞的,如果發現 bgrewriteaof 子行程直接回傳;如果發現 bgsave 子行程則等 bgsave 執行完成后再執行 fork 操作
-
主行程的 fork 操作完成后,繼續處理其他命令,把新的寫命令同時追加到 aof_buf 和 aof_rewrite_buf 緩沖區中
-
- 在檔案重寫完成之前,主行程會繼續把寫命令追加到 aof_buf 緩沖區,根據 appendfsync 策略同步到舊的 AOF 檔案,這樣可以避免 AOF 重寫失敗造成資料丟失,保證原有的 AOF 檔案的正確性
- 由于 fork 操作運用寫時復制技術,子行程只能共享 fork 操作時的記憶體資料,主行程會把新命令追加到一個 aof_rewrite_buf 緩沖區中,避免 AOF 重寫時丟失這部分資料
-
子行程讀取 Redis 行程中的資料快照,生成寫入命令并按照命令合并規則批量寫入到新的 AOF 檔案
-
子行程寫完新的 AOF 檔案后,向主行程發信號,主行程更新統計資訊,具體可以通過 info persistence 查看
-
主行程接受到子行程的信號以后,將 aof_rewrite_buf 緩沖區中的寫命令追加到新的 AOF 檔案
-
主行程使用新的 AOF 檔案替換舊的 AOF 檔案,AOF 重寫程序完成
3. 壓碩訓制
檔案重寫之所以能夠壓縮 AOF 檔案的大小,原因在于以下幾方面:
- 過期的資料不再寫入 AOF 檔案
- 無效的命令不再寫入 AOF 檔案,比如:重復為資料設值(set mykey v1, set mykey v2)、洗掉鍵值對資料(sadd myset v1, del myset)等等
- 多條命令可以合并為單個,比如:sadd myset v1, sadd myset v2, sadd myset v3 可以合并為 sadd myset v1 v2 v3,不過為了防止單條命令過大造成客戶端緩沖區溢位,對于 list、set、hash、zset 型別的 key,并不一定只使用單條命令,而是以某個 Redis 定義的一個常量為界,將命令拆分為多條
3.3. AOF 常用的配置項
下面是 redis.conf 檔案中和 AOF 檔案相關的常用配置項(以及默認值):
- appendonly no:是否開啟 AOF 持久化功能
- appendfilename "appendonly.aof":AOF 檔案的名稱
- dir ./:RDB 檔案和 AOF 檔案所在目錄
- appendfsync everysec:fsync 持久化策略
- no-appendfsync-on-rewrite no:重寫 AOF 檔案期間是否禁止 fsync 操作,如果開啟該選項,可以減輕檔案重寫時 CPU 和磁盤的負載(尤其是磁盤),但是可能會丟失 AOF 重寫期間的資料,需要在負載和安全性之間進行平衡
- auto-aof-rewrite-percentage 100:AOF 檔案重寫觸發條件之一
- auto-aof-rewrite-min-size 64mb:AOF 檔案重寫觸發條件之一
- aof-load-truncated yes:如果 AOF 檔案結尾損壞,Redis 服務器在啟動時是否仍載入 AOF 檔案
4. 資料恢復機制
前面提到當 AOF 持久化功能開啟時,Redis 服務器啟動時優先執行 AOF 檔案的命令恢復資料,只有當 AOF 功能關閉時,才會優先載入 RDB 快照的檔案資料,
- 當 AOF 功能關閉,且 RDB 持久化開啟時,Redis 服務器啟動日志:
6266:M 15 Sep 2019 08:30:41.832 # Server initialized
6266:M 15 Sep 2019 08:30:41.833 * DB loaded from disk: 0.001 seconds
6266:M 15 Sep 2019 08:30:41.833 * Ready to accept connections
- 當 AOF 功能開啟,且 AOF 檔案存在時,Redis 服務器啟動日志:
9447:M 15 Sep 2019 23:01:46.601 # Server initialized
9447:M 15 Sep 2019 23:01:46.602 * DB loaded from append only file: 0.001 seconds
9447:M 15 Sep 2019 23:01:46.602 * Ready to accept connections
- 當 AOF 功能開啟,且 AOF 檔案不存在時,即使 RDB 檔案存在也不會加載,Redis 服務器啟動日志:
9326:M 15 Sep 2019 22:49:24.203 # Server initialized
9326:M 15 Sep 2019 22:49:24.203 * Ready to accept connections
5. RDB 和 AOF 對比
| 持久化機制 | RDB | AOF |
|---|---|---|
| 啟動優先級 | 低 | 高 |
| 磁盤檔案體積 | 小 | 大 |
| 資料還原速度 | 快 | 慢 |
| 資料安全性 | 容易丟失資料 | 根據策略決定 |
| 操作輕重級別 | 重 | 輕 |
5.1. RDB 的優缺點
5.1.1. 優點
- RDB 是一個壓縮過的非常緊湊的檔案,保存著某個時間點的資料集,適合做資料的備份、災難恢復
- 可以最大化 Redis 的性能,在保存 RDB 檔案,服務器行程只需 fork 一個子行程來完成 RDB 檔案的創建,父行程不需要做 IO 操作
- 與 AOF 持久化方式相比,恢復大資料集的時候會更快
5.1.2. 缺點
- RDB 的資料安全性是不如 AOF 的,保存整個資料集是個重量級的程序,根據配置可能要幾分鐘才進行一次持久化,如果服務器宕機,那么就可能丟失幾分鐘的資料
- Redis 資料集較大時,fork 的子行程要完成快斬訓比較耗費 CPU 和時間
5.2. AOF 的優缺點
5.2.1. 優點
- 資料更完整,安全性更高,秒級資料丟失(取決于 fsync 策略,如果是 everysec,最多丟失 1 秒的資料)
- AOF 檔案是一個只進行追加的命令檔案,且寫入操作是以 Redis 協議的格式保存的,內容是可讀的,適合誤刪緊急恢復
5.2.2. 缺點
- 對于相同的資料集,AOF 檔案的體積要遠遠大于 RDB 檔案,資料恢復也會比較慢
- 根據所使用的 fsync 策略,AOF 的速度可能會慢于 RDB,不過在一般情況下, 每秒 fsync 的性能依然非常高
6. RDB-AOF 混合持久化
在重啟 Redis 服務器時,一般很少使用 RDB 快照檔案來恢復記憶體狀態,因為會丟失大量資料,更多的是使用 AOF 檔案進行命令重放,但是執行 AOF 命令性能相對 RDB 來說要慢很多,這樣在 Redis 資料很大的情況下,啟動需要消耗大量的時間,
鑒于 RDB 快照可能會造成資料丟失,AOF 指令恢復資料慢,Redis 4.0 版本提供了一套基于 AOF-RDB 的混合持久化機制,保留了兩種持久化機制的優點,這樣重寫的 AOF 檔案由兩部份組成,一部分是 RDB 格式的頭部資料,另一部分是 AOF 格式的尾部指令,
Redis 4.0 版本的混合持久化功能默認是關閉的,通過配置 aof-use-rdb-preamble 為 yes 開啟此功能:
# 開啟AOF-RDB混合持久化機制
aof-use-rdb-preamble yes
查看 Redis 服務器是否開啟混合持久化功能:
127.0.0.1:6379> CONFIG GET aof-use-rdb-preamble
1) "aof-use-rdb-preamble"
2) "yes"
如圖所示,將 RDB 資料檔案的內容和增量的 AOF 命令檔案存在一起,這里的 AOF 命令不再是全量的命令,而是自持久化開始到持久化結束的這段時間服務器行程執行的增量 AOF 命令,通常這部分 AOF 命令很小,

在 Redis 服務器重啟的時候,可以預先加載 AOF 檔案頭部全量的 RDB 資料,然后再重放 AOF 檔案尾部增量的 AOF 命令,從而大大減少了重啟程序中資料還原的時間,
7. 持久化策略選擇
7.1. RDB 和 AOF 性能開銷
在介紹持久化策略之前,首先要明白無論是 RDB 還是 AOF 方式,開啟持久化都是會造成性能開銷的,
-
RDB 持久化:
-
- BGSAVE 命令在進行 fork 操作時,Redis 服務器主行程會發生阻塞
- Redis 子行程向磁盤寫入資料也會帶來 IO 壓力
-
AOF 持久化:
-
- 向磁盤寫入資料的頻率大大提高,IO 壓力更大,甚至可能造成 AOF 追加阻塞問題
- AOF 檔案重寫與 RDB 的 BGSAVE 程序類似,存在父行程 fork 時的阻塞和子行程的 IO 壓力問題
相對來說,由于 AOF 向磁盤中寫入資料的頻率更高,因此對 Redis 服務器主行程性能的影響會更大,
7.2. 持久化策略
在實際生產環境中,根據資料量、應用對資料的安全要求、預算限制等不同情況,會有各種各樣的持久化策略,
- 完全不使用任何持久化功能
- 使用 RDB 或 AOF 其中一種
- 同時開啟 RDB 和 AOF 持久化
對于分布式環境,持久化的選擇必須與 Redis 的主從策略一起考慮,因為主從復制與持久化同樣具有資料備份的功能,而且主節點(Master Node)和從節點(Slave Node)可以獨立選擇持久化方案,
下面分場景來討論持久化策略的選擇,下面的討論也只是作為參考,實際方案可能更復雜更具多樣性,
7.2.1. 資料庫快取
如果 Redis 中的資料完全丟棄也沒有關系(如 Redis 完全用作 DB 層資料的快取),那么無論是單機,還是主從架構,都可以不進行任何持久化,
7.2.2. 單機環境
在單機環境下,如果可以接受十幾分鐘或更多的資料丟失,RDB 方案對 Redis 的性能更加有利;如果只能接受秒級別的資料丟失,選擇 AOF 方案更合適,
7.2.3. 主從部署
在多數情況下,Redis 都會配置主從部署機制,從節點(slave)既可以實作資料的熱備,也可以進行讀寫分擔 Redis 讀請求,以及在主節點(master)宕機后的頂替作用,
在這種情況下,一種可行的做法如下:
- master:完全關閉持久化(包括 RDB 和 AOF 功能),這樣可以讓主節點的性能達到最好
- slave:關閉 RDB 功能,開啟 AOF 功能(如果對資料安全要求不高,開啟 RDB 關閉 AOF 也可以),定時對持久化檔案進行備份(如備份到其他檔案夾,并標記好備份的時間),然后關閉 AOF 的自動重寫功能,然后添加定時任務,在每天 Redis 服務器閑時(如凌晨 12 點)呼叫 bgrewriteaof 手動重寫,
為什么開啟了主從復制,可以實作資料的熱備份,還需要設定持久化呢?因為在一些特殊情況下,主從復制仍然不足以保證資料的安全,例如:
-
master 和 slave 同時停止:如果 master 節點和 slave 節點位于同一個機房,則一次停電事故就可能導致 master 和 slave 機器同時關機,Redis 服務器行程停止,如果沒有持久化,則面臨的是資料的完全丟失,
-
master 重啟:如果 master 節點因為故障宕機,并且系統中有自動拉起機制(即檢測到服務停止后重啟該服務)將 master 節點自動重啟,
-
- 由于沒有持久化檔案,那么 master 重啟后資料是空的,slave 同步資料也變成了空的
- 如果 master 和 slave 節點都沒有開啟持久化,同樣會引發資料的完全丟失
7.2.4. 異地備災
前面的幾種持久化策略,針對的都是一般的系統故障,如行程例外退出、宕機、斷電等,這些故障不會損壞硬碟,但是對于一些可能導致硬碟損壞的災難情況,如火災地震,就需要進行異地災備,
- 單機環境:可以定時將 RDB 檔案或重寫后的 AOF 檔案,通過 scp 命令拷貝到遠程機器,如阿里云、AWS 等
- 主從部署,可以定時在 master 節點上執行 BGSAVE 操作,然后將 RDB 檔案拷貝到遠程機器,或者在 slave 節點上執行 bgrewriteaof 命令重寫 AOF 檔案后,將 AOF 檔案拷貝到遠程機器上,
由于 RDB 檔案檔案小、恢復速度快,災難恢復一般采用 RDB 方式;異地備份的頻率根據資料安全性的需要及其它條件來確定,但最好不要低于一天一次,
小結
本文主要開篇介紹了 Redis 服務器的資料庫結構,進一步介紹了 Redis 提供的幾種持久化機制,包括基于資料快照的 RDB 全量持久化、基于命令追加的 AOF 增量持久化以及 Redis 4.0 支持的混合持久化,對于 RDB 的持久化方式,給出了 RDB 快照的創建和還原程序,RDB 的檔案結構以及相關配置項,對于 AOF 的持久化方式,給出了 AOF 日志的創建和還原程序,AOF 的執行流程,AOF 檔案內部的格式以及相關配置項,在文章結尾分析了 RDB 和 AOF 方式各自的優缺點,性能開銷,以及在單機環境、主從部署、異地備災場景下的持久化策略,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/22952.html
標籤:NoSQL
