redis兩種持久化方式RDB和AOF

目錄

  • 前言redis

  • 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 兩種持久化方案,包括操做方法和持久化的實現原理。數據庫

img

正文

Redis 是一個基於鍵值對(K-V)存儲的數據庫服務器,下面先介紹 Redis 數據庫的內部構造以及 K-V 的存儲形式,有助於咱們更容易理解 Redis 的持久化機制。緩存

1. Redis 數據庫結構

一個單機的 Redis 服務器默認狀況下有 16 個數據庫(0-15 號),數據庫的個數是可配置的。Redis 默認使用的是 0 號數據庫,可使用 SELECT 命令切換數據庫。安全

imgRedis 中的每一個數據庫都由一個 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 類型表示。架構

img

2. RDB 持久化

RDB 持久化(也稱做快照持久化)是指將內存中的數據生成快照保存到磁盤裏面,保存的文件後綴是 .rdb。rdb 文件是一個通過壓縮的二進制文件,當 Redis 從新啓動時,能夠讀取 rdb 快照文件恢復數據。RDB 功能最核心的是 rdbSave 和 rdbLoad 兩個函數, 前者用於生成 RDB 文件並保存到磁盤,然後者則用於將 RDB 文件中的數據從新載入到內存中:app

img

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 命令生成快照的流程:

img

  1. 客戶端發起 BGSAVE 命令,Redis 主進程判斷當前是否存在正在執行備份的子進程,若是存在則直接返回
  2. 父進程 fork 一個子進程 (fork 的過程當中會形成阻塞的狀況),這個過程可使用 info stats 命令查看 latest_fork_usec 選項,查看最近一次 fork 操做消耗的時間,單位是微秒
  3. 父進程 fork 完成以後,則會返回 Background saving started 的信息提示,此時 fork 阻塞解除
  4. fork 建立的子進程開始根據父進程的內存數據生成臨時的快照文件,而後替換原文件
  5. 子進程備份完畢後向父進程發送完成信息,父進程更新統計信息
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 文件還原數據庫狀態。

img

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 文件的完整性

img

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 部分保存了一個或者多個鍵值對,若是鍵值對有過時時間,過時時間會被保存在鍵值對的前面。下面是這兩種鍵值對的內部結構:

img

  • 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 文件中的命令來恢復數據。

img

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 文件內部的構造圖:

img

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 持久化的執行流程。

img

AOF 文件的寫入流程能夠分爲如下 3 個步驟:

  1. 命令追加(append):將 Redis 執行的寫命令追加到 AOF 的緩衝區 aof_buf
  2. 文件寫入(write)和文件同步(fsync):AOF 根據對應的策略將 aof_buf 的數據同步到硬盤
  3. 文件重寫(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 文件重寫的流程以下:

img

  1. 客戶端經過 bgrewriteaof 命令對 Redis 主進程發起 AOF 重寫請求

  2. 當前不存在正在執行 bgsave/bgrewriteaof 的子進程時,Redis 主進程經過 fork 操做建立子進程,這個過程主進程是阻塞的。若是發現 bgrewriteaof 子進程直接返回;若是發現 bgsave 子進程則等 bgsave 執行完成後再執行 fork 操做

  3. 主進程的 fork 操做完成後,繼續處理其餘命令,把新的寫命令同時追加到 aof_buf 和 aof_rewrite_buf 緩衝區中

    • 在文件重寫完成以前,主進程會繼續把寫命令追加到 aof_buf 緩衝區,根據 appendfsync 策略同步到舊的 AOF 文件,這樣能夠避免 AOF 重寫失敗形成數據丟失,保證原有的 AOF 文件的正確性
    • 因爲 fork 操做運用寫時複製技術,子進程只能共享 fork 操做時的內存數據,主進程會把新命令追加到一個 aof_rewrite_buf 緩衝區中,避免 AOF 重寫時丟失這部分數據
  4. 子進程讀取 Redis 進程中的數據快照,生成寫入命令並按照命令合併規則批量寫入到新的 AOF 文件

  5. 子進程寫完新的 AOF 文件後,向主進程發信號,主進程更新統計信息,具體能夠經過 info persistence 查看

  6. 主進程接受到子進程的信號之後,將 aof_rewrite_buf 緩衝區中的寫命令追加到新的 AOF 文件

  7. 主進程使用新的 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 命令很小。

img

在 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. 持久化策略

在實際生產環境中,根據數據量、應用對數據的安全要求、預算限制等不一樣狀況,會有各類各樣的持久化策略。

  1. 徹底不使用任何持久化功能
  2. 使用 RDB 或 AOF 其中一種
  3. 同時開啓 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 方式各自的優缺點,性能開銷,以及在單機環境、主從部署、異地備災場景下的持久化策略。

相關文章
相關標籤/搜索