redis兩種持久化方式RDB和AOF

来源:https://www.cnblogs.com/eternityz/archive/2020/02/07/12273159.html
-Advertisement-
Play Games

目錄 前言 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. 存儲 ...


目錄

  • 前言

  • 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 文件中的數據重新載入到記憶體中:

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 方式各自的優缺點,性能開銷,以及在單機環境、主從部署、異地備災場景下的持久化策略。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • .NET Framework 4.5.2 框架 資料庫安裝windows7安裝mysql時需要 迅雷下載鏈接: https://download.microsoft.com/download/E/2/1/E21644B5 2DF2 47C2 91BD 63C560427900/NDP452 KB29 ...
  • 網上對於此類的文章已經十分飽和了,那還寫的原因很簡單——作為自己的理解筆記。 前言 ​ 此篇文章作為自己學習 的一些個人理解,使用的引擎是 。首先先講講 事務 的概念,在 中其對 事務 的描述是這樣的: 事務就是一組原子性的SQL查詢,或者說一個獨立的工作單元。如果資料庫引擎能夠成功地對資料庫應用該 ...
  • keys /查看所有的key exists key名字/判斷某個key是否存在 move key db/將可以從當前庫移動到庫db expire key 秒數/為給定的key設置過期時間 ttl key/查看key還有多長時間過期, 1表示永不過期, 2不是已經過期 type key/查看key是什 ...
  • 今天在使用命令slaveof或者是replicaof命令配置redis主從複製時,從機出現master_link_status:down提示,顯示主機是down的狀態,主機顯示沒有從機掛載。 主要是因為這裡的redis配置了密碼,可以在slave的配置文件里指定(配從不配主) 將master和sla ...
  • 簡介 從庫配置 主從同步一 一主多從(同一臺機器上同一個redis運行在三個port上) 1.複製redis配置文件三份 2.啟動三個redis 3.細節(暫時一主一從) redis配置文件中帶有密碼時即requirepass時,如果配置文件不特殊配置(masterauth不配置),此處為常規配置 ...
  • 簡介 示例,訂閱多個 通過正則匹配訂閱多個 ...
  • website相當於map的名字 google相當於map website中的key,"www.google.com"是key google的value ...
  • select切換資料庫 dbsize查看當前資料庫的key數量 keys查看所有的key flushdb刪除當前庫所有的key flushall刪除所有庫所有的key redis索引都是從0開始 預設埠是6379 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...