Redis穩定性之戰:AOF日誌支撐資料持久化

Hello-Brand發表於2024-03-12

Redis24篇集合

1 介紹

AOF(Append Only File)持久化:以獨立日誌的方式儲存了 Redis 伺服器的順序指令序列,並只記錄對記憶體進行修改的指令。
當Redis服務發生雪崩等故障時,可以重啟服務並重新執行AOF檔案中的指令達到恢復資料的目的。也就是說,透過重放(replay),來重新建立 Redis 當前例項的記憶體資料結構。這種模式有沒有很熟悉,可以聯想到MySQL主從同步時的relay log。
相對於咱們上一篇介紹的《RDB記憶體快照提供持久化能力》定點快照的做法,AOF的主要作用是解決了資料持久化的實時性,目前已經是Redis持久化的主流方式。

2 AOF實現日誌記錄

2.1 開啟AOF日誌記錄

1、 開啟AOF日誌記錄:在redis.conf檔案中,找到 APPEND ONLY MODE 設定

appendonly yes  # 預設不開啟, 為 no

2、配置預設檔名:在redis.conf檔案中設定

appendfilename “appendonly.aof”

2.2 執行流程

image

流程如上圖所示,我們解析如下:

2.2.1 將所有的寫命令(set、hset)Append 到aof_buf緩衝區中

Redis 接收到 set keyName someValue 命令的時候,會先將資料寫到記憶體,Redis 會按照如下格式寫入 AOF 檔案。

  1. *3:表示當前指令分為三個部分,每個部分都是 $ + 數字開頭,後面是3部分的具體內容:指令、鍵、值。
  2. 數字:表示這部分的命令、鍵、值多佔用的位元組大小。比如 $3表示這部分包含 3 個字元,也就是 set 的長度。

我們看看一個典型的aof檔案示例,為了清晰表示,下面的註釋都是手動加的:

[root@localhost bin]#vim appendonly.aof
#  執行 set key value
*3
$3           # 這邊代表set命令,長度為3
set
$9 
user_name      # 這邊代表keyName,長度為9
$5 
brand      #  這邊代表keyValue,長度為5


# 執行 mset key1 1 ,key2 2 ,key33 3
# aof日誌如下:
*7  # 本批命令需要往下讀7行非 $ 開始的命令
$4  #接著讀取4個位元組寬度,‘mset’長度為4,記為 $4
mset
$4  #接著讀取4個位元組寬度,‘key1’長度為4,記為 $4
key1
$1  #接著讀取1個位元組寬度,‘1’長度為1,記為 $1
1
$4
key2
$1
2
$5  #接著讀取的位元組寬度,‘$key33’長度為5,記為 $5
key33
$1
3

2.2.2 AOF緩衝區根據策略向硬碟做sync同步

AOF為什麼把命令append到aof_buf中,然後再進行同步?
這是因為Redis使用單程序響應命令(參考筆者這篇《深刻理解高效能Redis的本質》),如果每次寫AOF檔案命令都直接持久化到硬碟,那麼操作會是不是被間斷,且效能完全取決於硬碟I/O負載。這個跟 MySQL 就沒啥區別了。
先寫入緩衝區aof_buf中,Redis可以提供多種緩衝區同步硬碟的策略,在效能、安全、資料可靠性方面做出平衡。

同步策略需關注以下幾個配置:

1、 appendfsync 模式

appendfsync always  # 接受寫命令後立即寫入磁碟,強持久化但執行慢,不推薦
appendfsync everysec # 每秒寫入磁碟一次, 效能和持久化方面做了折中, 推薦
appendfsync no  #  依賴作業系統自身同步的配置和策略,效能較佳,但是沒法保證實時和完全持久化

2、no-appendfsync-on-rewrite
在 AOF 重寫期間是否禁用 fsync。這可以提高重寫效能,但可能會增加資料丟失的風險。

# 預設值:no
# 可選值:yes 或 no
no-appendfsync-on-rewrite yes

2.2.3 AOF檔案Rewrite實現壓縮

隨著AOF檔案越來越大,需要定期對AOF檔案進行重寫,達到壓縮減負的目的,避免AOF檔案過大導致效能和資料可靠性問題。
重寫後的AOF檔案變小的原因主要有以下幾點:
1、程序內已超時的資料不再寫入:在重寫過程中,Redis不會將已經超時的資料寫入新的AOF檔案,這有助於減少不必要的資料記錄。
2、刪除無效命令:舊的AOF檔案中可能包含無效的命令,如del key1hdel key2srem keysset a111等。重寫過程會識別並刪除這些無效命令,只保留最終資料的寫入命令,從而減小了檔案大小。
3、合併多條寫命令:為了進一步最佳化AOF檔案的大小,重寫過程會將多條寫命令合併為一個。例如,lpush list alpush list blpush list c可以合併為lpush list a b c。這種合併減少了命令的數量,進而減小了AOF檔案的大小。
4、防止單條命令過大:對於某些操作型別(如list、set、hash、zset),為了防止單條命令過大造成客戶端緩衝區溢位,重寫過程會以64個元素為界拆分多條命令。雖然這在一定程度上可能增加了命令的數量,但它確保了每條命令的大小都在可控範圍內,有助於維持整體檔案大小的合理性。
總之AOF重寫降低了檔案佔用空間,同時提升載入效能,因為更小的AOF 檔案可以更快地被Redis載入。

AOF重寫關注以下配置:
1、auto-aof-rewrite-percentage
觸發 AOF 重寫的增長百分比。例如,如果當前 AOF 檔案大小是 100MB,並且這個值設定為 100,那麼當 AOF 檔案增長到 200MB 時,說明增長了100%,Redis 會嘗試重寫 AOF。

# 預設值:`100`
`auto-aof-rewrite-percentage 100`

2、auto-aof-rewrite-min-size

AOF 檔案的最小大小,以便觸發重寫。即使 AOF 檔案的增長百分比超過了 auto-aof-rewrite-percentage 設定的值,但如果檔案大小小於這個值,Redis 也不會觸發重寫。

# 預設值:`64mb`
auto-aof-rewrite-min-size 64mb

image

2.2.4 故障重啟時的資料恢復

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

流程如下:

  1. 當AOF和RDB檔案同時存在時,優先載入AOF
  2. 若關閉了AOF(apendonly no),則載入RDB檔案
  3. 載入AOF/RDB成功之後,redis重啟成功。如果無相關的持久化,則直接啟動成功。
  4. 如果AOF/RDB 資料恢復存在錯誤,則啟動失敗,並列印輸出錯誤資訊

2.3 RDB和AOF的比較和混合持久化

咱們上一篇介紹了《RDB記憶體快照提供持久化能力》定點快照的使用者,那RDB跟AOF究竟孰優孰慮?
現實情況下,無論使用RDB或者AOF都差點意思。使用 rdb 來恢復記憶體狀態,勢必會丟失一部分資料。使用 AOF 日誌重放,重放對效能有一定的影響,而且在 Redis 例項很大的情況下,需要花費很長的時間。
Redis 4.0 解決了這個問題,才用了一個新的持久化模式——混合持久化,該 混合模式 預設是關閉狀態的。
將 RDB 檔案的內容和 rdb快照時間點之後的增量的 AOF 日誌檔案存在一起。這時候 AOF 日誌不需要再是全量的日誌,而是最近一次快照時間點之後到當下發生的增量 AOF 日誌,通常這部分 AOF 日誌很小。
所以執行有如下順序:

  • 查詢rdb內容,如果存在先載入 rdb內容再 重放剩餘的 aof。
  • 沒有rdb內容,直接以aof格式重放整個檔案。
    這樣快照就不用頻繁的執行,同時由於 AOF 只需要記錄最近一次快照之後的資料,不需要記錄所有的操作,避免了出現單次重放檔案過大的問題。

開啟混合持久化模式:

aof-use-rdb-preamble yes

這個設定告訴Redis在AOF重寫時使用混合持久化模式。當這個選項設定為yes時,重寫後的AOF檔案將包含RDB格式的資料字首和AOF格式的增量修改操作。

總結

  • RDB提供了快照模式,記錄某個時間的Redis記憶體狀態。RDB設計了 bgsave 和寫時複製,儘可能避免執行快照期間對讀寫指令的影響,但是頻繁快照會給磁碟帶來壓力以及 fork 阻塞主執行緒。需把握頻率。
  • AOF 日誌儲存了 Redis 服務的順序指令序列,透過重放(replay)指令來寫入日誌檔案,並透過寫回策略來避免高頻讀寫給Redis帶來壓力。
  • RDB快照的照片時間間隔,必然會帶來資料缺失,如果允許分鐘級別的資料丟失,可以只使用 RDB。
  • 如果只用 AOF,寫回策略優先使用 everysec 的配置選項,因為它在可靠性和效能之間取了一個平衡。
  • 資料不能丟失時,記憶體快照和 AOF 的混合使用是一個很好的選擇。

相關文章