Redis 學習筆記(四)RDB 和 AOF 持久化機制

Ethan_Wong發表於2022-02-07

一、Redis 持久化簡介

Redis 的持久化功能是區別於 Memcached 顯著特性,資料持久化可以保證系統在發生當機和重啟後資料不會丟失,對於 redis 這種儲存在記憶體中的資料庫顯得尤為重要。 在 Redis 4.0 以前資料持久化的方式主要有兩種

  • RDB(Redis DataBase)快照方式: 它是將某一時刻的記憶體資料以二進位制的方式寫入磁碟,預設儲存檔案為 dump.rdb

  • AOF(Append Only File)檔案追加方式:它是指將所有的操作命令,以文字的形式追加到檔案中。預設儲存檔案是 appendonly.aof

二、RDB 持久化機制

RDB 是將記憶體中的資料在某一時刻的狀態記錄下來以二進位制的方式儲存到磁碟中,通過生成一個經過壓縮的二進位制檔案來實現資料庫狀態的復原。預設是 dump.rdb 檔案,它的優點是以二進位制儲存,佔用空間更小,資料儲存更緊湊。

因為 RDB 檔案是儲存在磁碟中,哪怕 Redis 伺服器當機,Redis 伺服器就可以通過該檔案來還原資料庫狀態。

2.1 RDB 觸發方式

觸發RDB持久化既可以通過手動執行,也可以根據伺服器配置選項定期執行。主要分為手動觸發和自動觸發兩種方式。

2.1.1 手動觸發

手動觸發主要是通過save 和 bgsave 兩個命令來生成RDB 檔案

  1. save 命令
  • **會阻塞當前 Redis 伺服器,然後直到 RDB 檔案建立完畢為止 **。對於記憶體較大的例項會造成長時間阻塞。
  1. bgsave 命令
  • 該命令會派生fork出一個子程式,由子程式負責建立RDB檔案,伺服器(父執行緒)繼續處理命令請求。RDB 持久化過程由子程式負責,完成後自動結束,阻塞階段只發生在 fork 階段,阻塞時間較短。所以大多數情況使用bgsave 命令生成RDB檔案

2.1.2 自動觸發

自動觸發主要是通過 redis.conf 配置檔案來完成:

# save <seconds> <changes> 週期性執行RDB持久化條件的設定格式
# 預設配置
save 900 1
save 300 10
save 60 10000

# 檔名稱
dbfilename dump.rdb

# 檔案儲存路徑
dir /home/work/app/redis/data/

# 如果持久化出錯,主程式是否停止寫入
stop-writes-on-bgsave-error yes

# 是否壓縮
rdbcompression yes

# 匯入時是否檢查,這裡是使用CRC64演算法來進行資料校驗,會增加效能消耗
rdbchecksum yes

還有其他情況也可能會導致自動觸發:

  • 主從複製時,從節點要從主節點進行全量複製時也會觸發 bgsave 操作,生成當時的快照 RDB 檔案傳送到從節點
  • 執行debug reload 命令重新載入 redis 時也會觸發 bgsave 操作,生成 RDB 檔案
  • 預設情況下執行 shutdown 命令時,若沒有開啟 AOF 持久化,也會觸發 bgsave 操作,生成 RDB 檔案

2.2 RDB 實戰問題

1.在某一時刻給資料量較大記憶體進行快照,此時伺服器也會收到資料寫請求,如何保證資料一致性?

資料量較大時進行快照,用時相對會比較長。如果伺服器在這個期間收到寫請求,那麼就不能保證快照的完整性。那麼Redis 是如何做的?

Redis 使用的是作業系統提供的寫時複製技術(Copy-On-Write, COW),在執行快照的同時,正常處理寫操作。

如下圖,針對 bgsave 命令觸發 RDB 機制來說,如果主執行緒要修改一塊資料(圖中的鍵值對 C),這塊資料就會被複制一份,生成該資料的副本(鍵值對C')。此時中執行緒在該副本上進行修改,bgsave 子執行緒繼續把原來的資料(鍵值對 C)寫入 RDB 檔案中。

這樣既保證了RDB 檔案的完整性,也允許主執行緒同時對資料進行修改,避免了對正常業務的影響。

2.在進行 RDB 快照的過程中,發生服務崩潰了怎麼辦?

資料沒有全部寫入磁碟前,該次快照操作都是失敗的。將以這次快照操作前的完整 RDB 快照檔案作為恢復記憶體的資料。也就是此次快照操作過程不能影響上一次的備份資料。實際上在操作時 Redis 服務會在磁碟上建立一個臨時檔案來進行資料操作,待操作成功後才會用這個臨時檔案替換上一次的備份資料。

3.可以每秒做一次快照嗎?

頻繁的執行全量快照,能夠帶來更快的恢復速度。但同時也會帶來大量的開銷,給磁碟帶來壓力,多個快照競爭有限的磁碟頻寬,容易造成惡性迴圈。此外, bgsave 子程式需要通過 fork 操作從主執行緒建立出來。如果頻繁的fork 出 bgsave 子程式就會阻塞主執行緒。因此 Redis 在有一個 bgsave 執行時,就不會再啟動第二個 bgsave 子程式。

這裡就可以採用增量快照的方法,就是在全量快照後,後續的快照只對修改後的資料進行快照,避免對資料重複的那部分進行快照帶來的消耗,這樣更加高效。這裡就需要對修改的檔案資料進行記錄,如下圖所示:

但是,我們在增量快照時,記錄所修改的資料資訊也是一部分的開銷,在大量資料修改時的記錄資料,其記憶體開銷也不少。所以對於做快照的頻率,不能太高也不能太低。那麼有什麼解決方法既能夠讓 RDB 快速恢復,又能以較小的開銷實現少丟資料呢?

Redis 4.0 提出了 混合使用 AOF 日誌和記憶體快照的方法可以很好的解決上面的場景。

三、AOF 持久化機制

AOF (Append Only File)日誌和大多數的資料庫寫入日誌的方式不同,採用的是寫後日志。(比如 MySQL 是通過寫前日誌(Write Ahead Log, WAL)和兩階段提交來實現資料和邏輯的一致性)

寫後日志的意思是 Redis 先將資料寫入記憶體,然後再記錄日誌,如下圖:

採用後寫日誌有這樣的好處:

  • 避免額外的檢查開銷:比如在我們輸入命令時可能會出錯,如果先記錄日誌再執行命令,日誌中可能記錄錯誤的命令,在利用日誌恢復資料時可能會出錯。那先命令,後記錄日誌就可以當命令執行成功再記錄,避免出現記錄錯誤命令的情況
  • 不會阻塞當前寫操作:因為它是在命令執行後才記錄日誌,不會執行當前的寫操作

但是在寫回磁碟時也會產生風險:

  • 在命令執行和寫日誌中間出現當機,執行命令資料會發生丟失
  • 雖然避免當前命令的阻塞,但是可能會給下一個操作帶來阻塞風險。磁碟寫壓力大,會導致寫盤很慢,進而導致後續操作無法執行。

這兩個風險是 AOF 寫回磁碟時機不當造成的,對於這兩個風險,AOF 有三種寫回策略可以避免。也就是在檔案寫入同步時發生。

3.1 AOF 持久化的實現

AOF 持久化功能的實現可以分為命令追加(append)、檔案寫入(write)和檔案同步(sync)三個步驟。

3.1.1 命令追加

當 AOF 持久化功能處於開啟狀態時,伺服器在執行完一個寫命令之後,就會以協議格式將被執行的寫命令追加到伺服器狀態的 aof_buf 緩衝區的末尾中,這個 aof_buf 是以 sds 物件形式儲存。

struct redisServer {
    ...
    // AOF 緩衝區
    sds aof_buf;
    ...
};

以Redis 服務端收到 set testkey testvalue 命令後的記錄為例,檢視 AOF 日誌的執行原理。

在伺服器執行 set 命令後,會將以下協議內容追加到 aof_buf 緩衝區的末尾:

*3\r\n$3\r\nSET\r\n$7\r\ntestkey\r\n$9\r\ntestvalue\r\n

  • *3 :表示當前命令有三個部分,每個部分都是由$+數字開頭,後面緊跟具體的命令、鍵或值
  • \r\n:是換行符,具體如上圖所示
  • $+數字:數字表示這部分的命令、鍵或值一共有多少個位元組,比如 $3 set 表示這部分有三個位元組的命令,也就是 set 命令

3.1.2 檔案寫入和同步

Redis 的伺服器程式就是一個事件迴圈(loop),在處理檔案事件時可能會執行寫命令,所以在伺服器每次結束一個事件迴圈之前,它會呼叫 flushAppendOnlyFile 函式,考慮是否需要將 aof_buf 緩衝區中的內容寫入到AOF 檔案中去。

def eventLoop():
	while True:
		//處理檔案事件,接收命令請求以及傳送命令回覆
		//處理命令請求時可能會有新內容被追加到 aof_buf緩衝區中
		processFileEvents();
		//處理時間事件
		processTimeEvents();
		//考慮是否要將 aof_buf中的內容寫入和儲存到 AOF檔案裡面
		flushAppendOnlyFile();

flushAppendOnlyFile函式的行為有伺服器配置的 appendfsync 選項的值來決定。其值主要有三種:

3.1.3 AOF 檔案的載入與資料還原

在儲存了 AOF 檔案後,伺服器只需要重新執行 AOF 檔案中的寫命令後,就可以還原伺服器關閉之前的資料庫狀態,其具體步驟如下:

  1. 建立一個不帶網路連線的偽客戶端(fake client)
  2. 從 AOF 檔案中分析並讀取一條寫命令
  3. 使用 fake client 執行被讀出的 寫命令
  4. 重複執行步驟2和3,直到 AOF 命令中所有寫命令都被處理完畢

3.2 AOF 重寫機制

因為 AOF 是通過儲存被執行的寫命令來完成 Redis 持久化的,所以隨著 Redis 的執行時間越長,AOF 檔案中儲存的日誌內容會越來越多。會給 Redis 伺服器,帶來很大的影響。因此 Redis 提供了 AOF 檔案重寫功能解決這一痛點。

3.2.1 重寫機制是如何減少 AOF 檔案大小的?

重寫機制具有“多變一”的功能,也就是對舊日誌檔案中的多條命令,在重寫後的新日誌變成了一條命令。如下圖所示:

3.2.2 重寫會阻塞嗎?

AOF 重寫過程是由後臺子程式 bgrewriteaof 來完成,重寫過程是一個拷貝,兩處日誌:

  • 一個拷貝:是指每次執行重寫時,主執行緒 fork 出後臺的 bgrewriteaof 子程式。此時,fork 會把主執行緒的記憶體拷貝一份給 bgrewriteaof。所以這個子執行緒就可以在不影響主執行緒的情況下,逐一把拷貝的資料寫成操作,記入重寫日誌。

  • 兩處日誌:

    • 第一處日誌指的是正在使用的 AOF 日誌,Redis 會把這個操作寫到它的緩衝區。即使當機了,這個 AOF 日誌的操作仍然是齊全的可以用於恢復。

    • 第二處日誌指的是新 AOF 重寫日誌,這個操作也會被寫到重寫日誌的緩衝區。這樣,重寫日誌也不會丟失最新的操作

所以說在每次 AOF 重寫時,Redis 會先執行一份記憶體拷貝,用於重寫;然後使用兩個日誌保證在重寫過程中新寫入的資料不會丟失。同時因為 bgrewriteaof 子程式的出現,這個過程並不會阻塞主執行緒。

3.2.3 AOF 日誌何時會進行重寫?

有兩個配置項可以控制 AOF 重寫的觸發:

  • auto-aof-rewrite-min-size: 表示執行 AOF 重寫時檔案的最小大小,預設是 64MB
  • auto-aof-rewrite-percentage: 這個值是當前 aof 檔案和上一次重寫後 aof 檔案差值,再除以上一次重寫 aof 檔案大小。

四、RDB 和 AOF 區別與聯絡

4.1 RDB 和 AOF 混合機制

在介紹 RDB 機制時,我們瞭解到是否有一種方法既能利用 RDB 的快速恢復,又能以較小的開銷做到儘量少丟資料?也就是混合使用 RDB 和 AOF 兩種機制的方法:就是讓 RDB 快照以一定頻率執行,在兩次快照之間使用 AOF 日誌記錄這期間的所有命令操作。

如上圖所示,第一次全量快照後,在T1 和 T2 時刻修改用 AOF 日誌記錄,等到第二次全量快照時,可以清空 AOF 日誌。因為此時的修改都已經記錄到快照中,恢復時也就不再使用日誌了。

4.2 RDB 和 AOF 優缺點

RDB

  • 預設檔案是 dump.rdb。具備更快速的資料重啟恢復能力,以二進位制儲存佔用更小的磁碟空間,但是有資料丟失的風險

AOF

  • 預設儲存檔案是 appendonly.aof。儲存頻率更高,儲存資訊更容易懂,缺點是佔用空間大,重啟之後的資料恢復速度較慢。

所以 RDB 和 AOF 混合機制綜合了上述兩種機制的優缺點。

4.3 RDB 和 AOF 檔案的載入和還原

AOF 的載入和還原過程在 AOF 小節中有提到。現在談談同時存在兩者的情況,伺服器如何用哪個檔案來還原資料庫狀態:

  • 如果伺服器開啟了 AOF 持久化功能,優先使用 AOF 檔案,因為AOF 更新頻率通常要比 RDB 檔案要高
  • 只有當 AOF 持久化功能關閉時,伺服器才會使用 RDB 檔案來還原資料狀態

參考資料:

《redis 設計與實現》

https://baijiahao.baidu.com/s?id=1654694618189745916&wfr=spider&for=pc

https://time.geekbang.org/column/article/271839

相關文章