一文理解 Redis 持久化:RDB和AOF

2021-12-30 13:00:02

1. 概述

我們知道 Redis 是一個記憶體資料庫,也就意味著如果我們的電腦異常重新啟動或者伺服器宕機的情況下,儲存在 Redis 中的資料會丟失。

Redis 雖然是個記憶體資料庫,但是 Redis 支援 RDBAOF 兩種持久化機制,將資料寫往磁碟,可以有效地避免因程序退出造成的資料丟失問題,當下次重新啟動時利用之前持久化的檔案即可實現資料恢復。

2. RDB

RDB(Redis DataBase) 持久化是把當前程序資料生成快照儲存到硬碟的過程。什麼是快照?你可以理解成把當前時刻的資料拍成一張照片儲存下來。

RDB持久化是指在指定的時間間隔內將記憶體中的資料集快照寫入磁碟。也是預設的持久化方式,這種方式是就是將記憶體中資料以快照的方式寫入到二進位制檔案中,預設的檔名為dump.rdb

前面說了,RDB 是快照的方式來儲存資料的,因此對於 RDB 來說,提供了兩種觸發機制, 手動觸發和自動觸發。

手動觸發分別對應 savebgsave 命令。

2.1 save

save 命令會阻塞當前 Redis 伺服器,直到 RDB 過程完成為止。

image-20211227101107885

因此,對於記憶體比較大的範例會造成長時間阻塞,線上環境不建議使用

2.2 bgsave

bgsave 命令執行時,Redis 程序執行 fork 操作建立子程序,RDB 持久化過程由子程序負責,完成後自動結束。阻塞只發生在 fork 階段,一般時間很短。

image-20211227101614484

顯然 bgsave 命令是針對 save 阻塞問題做的優化。因此 Redis 內部所有的涉及 RDB 的操作都採用 bgsave 的方式。其具體流程如下:

  1. 執行 bgsave 命令,Redis 父程序判斷當前是否存在正在執行的子程序,如 RDB/AOF 子程序,如果存在,bgsave 命令直接返回。

  2. 父程序執行 fork 操作建立子程序,fork 操作過程中父程序會阻塞,通過 info stats 命令檢視 latest_fork_usec 選項,可以獲取最近一個 fork 操作的耗時,單位為微秒。

    image-20211227110942552

  3. 父程序 fork 完成後,bgsave 命令返回 **Background saving started **資訊並不再阻塞父程序,可以繼續響應其他命令。

  4. 子程序建立 RDB 檔案,根據父程序記憶體生成臨時快照檔案,完成後對原有檔案進行原子替換。執行 lastsave 命令可以獲取最後一次生成 RDB 的時間,對應 info 統計的 rdb_last_save_time 選項。

    127.0.0.1:6379> lastsave
    (integer) 1640574329
    
  5. 程序傳送訊號給父程序表示完成,父程序更新統計資訊。

2.3 自動觸發

除了執行命令手動觸發之外,Redis 內部還存在自動觸發 RDB 的持久化機制。自動觸發是由我們的組態檔來完成的,在redis.conf組態檔中,有如下設定:

  • save

    save m n,指在 m 秒內,如果有 n 個鍵發生改變,則自動觸發持久化(執行一次 bgsave 命令)。例如,save 60 1 則表明在 60 秒內,至少有一個鍵發生改變,就會觸發 RDB 持久化。

  • stop-writes-on-bgsave-error

    預設值為 yes。當啟用了 RDB 且最後一次後臺儲存資料失敗,Redis 是否停止接收資料。這會讓使用者意識到資料沒有正確持久化到磁碟上,否則沒有人會注意到災難(disaster)發生了。如果Redis重新啟動了,那麼又可以重新開始接收資料了。

  • rdbcompression

    預設值是 yes。對於儲存到磁碟中的快照,可以設定是否進行壓縮儲存。

  • dbfilename

    設定快照的檔名,預設是 dump.rdb。

  • dir

    設定快照檔案的存放路徑,這個設定項一定是個目錄,而不能是檔名,預設為./

2.4 RDB 檔案

RDB 生成的持久化檔案預設為dump.rdb,可在組態檔中設定,也可以通過以下命令查詢:

find / -name dump.rdb

# linux 下查詢
[root@localhost ~]# find / -name dump.rdb
/usr/local/redis/redis-6.2.6/src/dump.rdb

RDB 檔案儲存在 dir 設定指定的目錄下,檔名通過 dbfilename 設定指定。

可以通過執行 config set dir {newDir}config set dbfilename {newFileName}執行期動態執行,當下次執行時 RDB 檔案會儲存到新目錄。

Redis 預設採用 LZF 壓縮演演算法對生成的 RDB 檔案做壓縮處理,壓縮後的檔案遠遠小於記憶體大小,預設開啟,可以通過引數 config set rdbcompression {yes|no}動態修改。

雖然壓縮 RDB 會消耗 CPU,但可大幅降低檔案的體積,方便儲存到硬碟或通過網維示絡傳送給從節點,因此線上建議開啟。

如果 Redis 載入損壞的 RDB 檔案時拒絕啟動,並列印如下紀錄檔:

# Short read or 0OM loading DB. Unrecoverable error,aborting now.

這時可以使用 Redis 提供的 redis-check-dump 工具檢測 RDB 檔案並獲取對應的錯誤報告。

2.5 RDB 的優缺點

2.5.1 優點

  1. RDB 是一個緊湊壓縮的二進位制檔案,代表 Redis 在某個時間點上的資料快照。非常適用於備份,全量複製等場景。 比如每隔幾小時執行 bgsave 備份,並把 RDB 檔案拷貝到遠端機器或者檔案系統中(如 hdfs),用於災難恢復。
  2. 生成 RDB 檔案的時候,Redis 主程序會 fork 一個子程序來處理所有儲存工作,主程序不需要進行任何磁碟 IO操作。
  3. Redis 載入 RDB 恢復資料遠遠快於 AOF 的方式。

2.5.2 缺點

  1. RDB 方式資料沒辦法做到實時持久化/秒級持久化。因為 bgsave 每次執行都要執行 fork 操作建立子程序,屬於重量級操作,頻繁執行成本過高。
  2. RDB 檔案使用特定二進位制格式儲存,Redis 版本演進過程中有多個格式的 RDB 版本,存在老版本 Redis 服務無法相容新版 RDB 格式的問題。

針對 RDB 不適合實時持久化的問題,Redis 提供了 AOF 持久化方式來解決。

3. AOF

AOF(Append Only File)持久化,以獨立紀錄檔的方式記錄每次寫命令,重新啟動時再重新執行 AOF 檔案中的命令達到恢復資料的目的。

AOF 的主要作用是解決了資料持久化的實時性,目前已經是 Redis 持久化的主流方式。相比 RDB 的全量備份節省了很多時間。

開啟 AOF 功能需要設定設定,預設不開啟。AOF 檔名通過 appendfilename 設定設定,預設檔名是 appendonly.aof。儲存路徑同 RDB 持久化方式一致,通過 dir 設定指定。

# 開啟 AOF
appendonly yes

# AOF 檔名
appendfilename "appendonly.aof"

# always   每收到寫命令就立即強制寫入磁碟,最慢的,但是保證完全的持久化,不推薦使用
# everysec 每秒強制寫入磁碟一次,效能和持久化方面做了折中,推薦
appendfsync everysec

# 正在匯出rdb快照的過程中,要不要停止同步aof
no-appendfsync-on-rewrite  yes

# aof檔案大小比起上次重寫時的大小,增長率100%時,重寫
auto-aof-rewrite-percentage 100

# aof檔案,至少超過64M時,重寫
auto-aof-rewrite-min-size 64mb

3.1 工作流程

AOF 的工作流程如下:

  1. 命令寫入(append)

    所有的寫入命令會追加到 aof_buf(緩衝區)中。

  2. 檔案同步(sync)

    AOF 緩衝區根據對應的策略向硬碟做同步操作。

  3. 檔案重寫(rewrite)

    隨著 AOF 檔案越來越大,需要定期對 AOF 檔案進行重寫,達到壓縮的目的。

  4. 重新啟動載入(load)

    當 Redis 伺服器重新啟動時,可以載入 AOF 檔案進行資料恢復。

image-20211227115404636

3.2 命令寫入

AOF 命令寫入的內容直接是 RESP 文字協定格式。例如 set hello world 這條命令,在 AOF 緩衝區會追加如下文字:

* 3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n

AOF 為什麼直接採用文字協定格式?

文字協定具有很好的相容性。開啟 AOF 後,所有寫入命令都包含追加操作,直接採用協定格式,避免了二次處理開銷。文字協定具有可讀性,方便直接修改和處理。

AOF 為什麼把命令追加到 aof_buf 中?

Redis 使用單執行緒響應命令,如果每次寫 AOF 檔案命令都直接追加到硬碟,那麼效能完全取決於當前硬碟負載。先寫入緩衝區 aof_buf 中,還有另一個好處,Redis 可以提供多種緩衝區同步硬碟的策略,在效能和安全性方面做出平衡。

1

3.3 檔案同步

Redis 提供了多種 AOF 緩衝區同步檔案策略,由引數 appendfsync 控制。主要有以下三種:

  1. always

    命令寫入 aof_buf 後呼叫系統 fsync 操作同步到 AOF 檔案,fsync 完成後執行緒返回命令 fsync 同步檔案。

    設定為 always 時,每次寫入都要同步 AOF 檔案,在一般的 SATA 硬碟上,Redis 只能支援大約幾百 TPS 寫入,顯然跟 Redis 高效能特性背道而馳,不建議設定。

  2. everysec

    寫人 aof_buf 後呼叫系統 write 操作,write 完成後執行緒返回。操作由專門執行緒每秒呼叫一次 fsync 命令。

    設定為 everysec,是建議的同步策略,也是預設設定,做到兼顧效能和資料安全性。理論上只有在系統突然宕機的情況下丟失 1 秒的資料。

  3. no

    寫入 aof_buf 後呼叫系統 write 操作,不對 AOF 檔案做 fsync 同步,同步硬碟操作由作業系統負責,通常同步週期最長 30 秒。

    設定為 no,由於作業系統每次同步 AOF 檔案的週期不可控,而且會加大每次同步硬碟的資料量,雖然提升了效能,但資料安全性無法保證。

系統呼叫 write 和 fsync

write

操作會觸發延遲寫(delayed write)機制。Linux 在核心提供頁緩衝區用來提高硬碟 IO 效能。write 操作在寫入系統緩衝區後直接返回。同步硬碟操作依賴於系統排程機制,例如:緩衝區頁空間寫滿或達到特定時間週期。同步檔案之前,如果此時系統故障宕機,緩衝區內資料將丟失。

fsync

針對單個檔案操作(比如 AOF 檔案),做強制硬碟同步,fsync 將阻塞直到寫入硬碟完成後返回,保證了資料持久化。

3.4 檔案重寫

隨著命令不斷寫入 AOF,檔案會越來越大,為了解決這個問題,Redis 引入 AOF 重寫機制壓縮檔案體積。AOF 檔案重寫是把 Redis 程序內的資料轉化為寫命令同步到新 AOF 檔案的過程。

1

重寫後的 AOF 檔案為什麼可以變小?原因如下:

  1. 程序內已經超時的資料不再寫入檔案。
  2. 舊的 AOF 檔案含有無效命令,如 set a 111、set a 222 等。重寫使用程序內資料直接生成,這樣新的 AOF 檔案只保留最終資料的寫入命令。
  3. 多條寫命令可以合併為一個,如:lpush list a、lpush list b、lpush listc 可以轉化為:lpush list a b c。為了防止單條命令過大造成使用者端緩衝區溢位,對於 list、set、hash、zset 等型別操作,以 64 個元素為界拆分為多條。

AOF 重寫降低了檔案佔用空間,除此之外,另一個目的是:更小的 AOF 檔案可以更快地被 Redis 載入。

AOF 重寫過程可以手動觸發和自動觸發:

  • 手動觸發:直接呼叫 bgrewriteaof 命令。

    該命令會將記憶體中的資料以命令的方式儲存到臨時檔案中,同時會 fork 出一條新程序來將檔案重寫。

  • 自動觸發:根據 auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage 引數確定自動觸發時機。

    • auto-aof-rewrite-min-size:表示執行 AOF 重寫時檔案最小體積,預設為 64 MB。
    • auto-aof-rewrite-percentage:代表當前 AOF 檔案空間(aof_currentsize)和上一次重寫後 AOF 檔案空間(aof_base_size)的比值。

3.5 重新啟動載入

AOF 和 RDB 檔案都可以用於伺服器重新啟動時的資料恢復。redis 重新啟動時載入 AOF 與 RDB 的順序是怎麼樣的呢?

  1. 當 AOF 和 RDB 檔案同時存在時,優先載入 AOF;
  2. 若關閉了 AOF,載入 RDB 檔案;
  3. 載入 AOF/RDB 成功,redis 重新啟動成功;
  4. AOF/RDB 存在錯誤,啟動失敗列印錯誤資訊。

image-20211227160201319

3.6 AOF 的優缺點

3.6.1 優點

  1. AOF 可以更好的保護資料不丟失,一般 AOF 會每隔 1 秒,通過一個後臺執行緒執行一次 fsync 操作,最多丟失 1 秒鐘的資料;
  2. AOF 紀錄檔檔案沒有任何磁碟定址的開銷,寫入效能非常高,檔案不容易破損;
  3. AOF 紀錄檔檔案即使過大的時候,出現後臺重寫操作,也不會影響使用者端的讀寫;
  4. AOF 紀錄檔檔案的命令通過非常可讀的方式進行記錄,這個特性非常適合做災難性的誤刪除的緊急恢復。比如某人不小心用 flushall 命令清空了所有資料,只要這個時候後臺 rewrite 還沒有發生,那麼就可以立即拷貝 AOF 檔案,將最後一條 flushall 命令給刪了,然後再將該 AOF 檔案放回去,就可以通過恢復機制,自動恢復所有資料。

3.6.2 缺點

  1. 對於同一份資料來說,AOF 紀錄檔檔案通常比RDB資料快照檔案更大。
  2. AOF 開啟後,支援的寫 QPS 會比 RDB 支援的寫 QPS 低,因為 AOF 一般會設定成每秒 fsync 一次紀錄檔檔案,當然,每秒一次 fsync,效能也還是很高的。
  3. 以前 AO F發生過 bug,就是通過 AOF 記錄的紀錄檔,進行資料恢復的時候,沒有恢復一模一樣的資料出來。

4. 如何選擇

對於 RDB,它能夠在指定的時間間隔對記憶體中的資料進行快照儲存。

對於 AOF,他能記錄每次對伺服器寫的操作,當伺服器重新啟動的時候會重新執行這些命令來恢復原始的資料,AOF 命令以文字協定追加儲存每次寫的操作到檔案末尾。Redis 還能夠對 AOF 檔案進行後臺重寫,使 AOF 檔案體積不至於過大。

兩種方式同時開啟,在兩種方式同時開啟的情況下,Redis 啟動的時候會優先載入 AOF 檔案來恢復原始資料,因為在通常情況下 AOF 檔案儲存的資料集比 RDB 檔案儲存的資料集要完整,RDB 的資料會不實時。

那麼只使用 AOF 呢?建議不要,因為 RDB 更適合用於備份資料庫(AOF 在不斷變化,不好備份),快速重新啟動,而不會有 AOF 可能存在的潛在 bug,留著作為一個補救的手段。

當然,如果你只是用作快取,只希望資料在程式執行的時候存在,那麼就可以不使用任何持久化方式。

選項RDBAOF
啟動優先順序
體積
恢復速度
資料安全丟資料三種策略

附:組態檔詳解

# redis程序是否以守護行程的方式執行,yes為是,no為否(不以守護行程的方式執行會佔用一個終端)。
daemonize no
# 指定redis程序的PID檔案存放位置
pidfile /var/run/redis.pid
# redis程序的埠號
port 6379
#是否開啟保護模式,預設開啟。要是設定裡沒有指定bind和密碼。開啟該引數後,redis只會本地進行存取,拒絕外部存取。要是開啟了密碼和bind,可以開啟。否則最好關閉設定為no。
protected-mode yes
# 繫結的主機地址
bind 127.0.0.1
# 使用者端閒置多長時間後關閉連線,預設此引數為0即關閉此功能
timeout 300
# redis紀錄檔級別,可用的級別有debug.verbose.notice.warning
loglevel verbose
# log檔案輸出位置,如果程序以守護行程的方式執行,此處又將輸出檔案設定為stdout的話,就會將紀錄檔資訊輸出到/dev/null裡面去了
logfile stdout
# 設定資料庫的數量,預設為0可以使用select <dbid>命令在連線上指定資料庫id
databases 16
# 指定在多少時間內重新整理次數達到多少的時候會將資料同步到資料檔案
save <seconds> <changes>
# 指定儲存至本地資料庫時是否壓縮檔案,預設為yes即啟用儲存
rdbcompression yes
# 指定本地資料庫檔名
dbfilename dump.db
# 指定本地資料問就按存放位置
dir ./
# 指定當本機為slave服務時,設定master服務的IP地址及埠,在redis啟動的時候他會自動跟master進行資料同步
replicaof <masterip> <masterport>
# 當master設定了密碼保護時,slave服務連線master的密碼
masterauth <master-password>
# 設定redis連線密碼,如果設定了連線密碼,使用者端在連線redis是需要通過AUTH<password>命令提供密碼,預設關閉
requirepass footbared
# 設定同一時間最大客戶連線數,預設無限制。redis可以同時連線的使用者端數為redis程式可以開啟的最大檔案描述符,如果設定 maxclients 0,表示不作限制。當用戶端連線數到達限制時,Redis會關閉新的連線並向用戶端返回 max number of clients reached 錯誤資訊
maxclients 128
# 指定Redis最大記憶體限制,Redis在啟動時會把資料載入到記憶體中,達到最大記憶體後,Redis會先嚐試清除已到期或即將到期的Key。當此方法處理後,仍然到達最大記憶體設定,將無法再進行寫入操作,但仍然可以進行讀取操作。Redis新的vm機制,會把Key存放記憶體,Value會存放在swap區
maxmemory<bytes>
# 指定是否在每次更新操作後進行紀錄檔記錄,Redis在預設情況下是非同步的把資料寫入磁碟,如果不開啟,可能會在斷電時導致一段時間內的資料丟失。因為redis本身同步資料檔案是按上面save條件來同步的,所以有的資料會在一段時間內只存在於記憶體中。預設為no。
appendonly no
# 指定跟新紀錄檔檔名預設為appendonly.aof
appendfilename appendonly.aof
# 指定更新紀錄檔的條件,有三個可選引數 - no:表示等作業系統進行資料快取同步到磁碟(快),always:表示每次更新操作後手動呼叫fsync()將資料寫到磁碟(慢,安全), everysec:表示每秒同步一次(折衷,預設值);
appendfsync everysec