Redis持久化機制 RDB、AOF、混合持久化詳解!如何選擇?

2023-07-27 21:00:27

本文已經收錄進 JavaGuide(「Java學習+面試指南」一份涵蓋大部分 Java 程式設計師所需要掌握的核心知識。)

Redis 持久化機制屬於後端面試超高頻的面試知識點,老生常談了,需要重點花時間掌握。即使不是準備面試,日常開發也是需要經常用到的。

最近抽空對之前寫的 Redis 持久化機制進行了大幅完善,圖文並茂,清晰易懂。分享一下,希望對你有幫助!

內容概覽:

使用快取的時候,我們經常需要對記憶體中的資料進行持久化也就是將記憶體中的資料寫入到硬碟中。大部分原因是為了之後重用資料(比如重啟機器、機器故障之後恢復資料),或者是為了做資料同步(比如 Redis 叢集的主從節點通過 RDB 檔案同步資料)。

Redis 不同於 Memcached 的很重要一點就是,Redis 支援持久化,而且支援 3 種持久化方式:

  • 快照(snapshotting,RDB)
  • 只追加檔案(append-only file, AOF)
  • RDB 和 AOF 的混合持久化(Redis 4.0 新增)

官方檔案地址:https://redis.io/topics/persistence

RDB 持久化

什麼是 RDB 持久化?

Redis 可以通過建立快照來獲得儲存在記憶體裡面的資料在 某個時間點 上的副本。Redis 建立快照之後,可以對快照進行備份,可以將快照複製到其他伺服器從而建立具有相同資料的伺服器副本(Redis 主從結構,主要用來提高 Redis 效能),還可以將快照留在原地以便重啟伺服器的時候使用。

快照持久化是 Redis 預設採用的持久化方式,在 redis.conf 組態檔中預設有此下設定:

save 900 1           #在900秒(15分鐘)之後,如果至少有1個key發生變化,Redis就會自動觸發bgsave命令建立快照。

save 300 10          #在300秒(5分鐘)之後,如果至少有10個key發生變化,Redis就會自動觸發bgsave命令建立快照。

save 60 10000        #在60秒(1分鐘)之後,如果至少有10000個key發生變化,Redis就會自動觸發bgsave命令建立快照。

RDB 建立快照時會阻塞主執行緒嗎?

Redis 提供了兩個命令來生成 RDB 快照檔案:

  • save : 同步儲存操作,會阻塞 Redis 主執行緒;
  • bgsave : fork 出一個子程序,子程序執行,不會阻塞 Redis 主執行緒,預設選項。

這裡說 Redis 主執行緒而不是主程序的主要是因為 Redis 啟動之後主要是通過單執行緒的方式完成主要的工作。如果你想將其描述為 Redis 主程序,也沒毛病。

AOF 持久化

什麼是 AOF 持久化?

與快照持久化相比,AOF 持久化的實時性更好。預設情況下 Redis 沒有開啟 AOF(append only file)方式的持久化(Redis 6.0 之後已經預設是開啟了),可以通過 appendonly 引數開啟:

appendonly yes

開啟 AOF 持久化後每執行一條會更改 Redis 中的資料的命令,Redis 就會將該命令寫入到 AOF 緩衝區 server.aof_buf 中,然後再寫入到 AOF 檔案中(此時還在系統核心快取區未同步到磁碟),最後再根據持久化方式( fsync策略)的設定來決定何時將系統核心快取區的資料同步到硬碟中的。

只有同步到磁碟中才算持久化儲存了,否則依然存在資料丟失的風險,比如說:系統核心快取區的資料還未同步,磁碟機器就宕機了,那這部分資料就算丟失了。

AOF 檔案的儲存位置和 RDB 檔案的位置相同,都是通過 dir 引數設定的,預設的檔名是 appendonly.aof

AOF 工作基本流程是怎樣的?

AOF 持久化功能的實現可以簡單分為 5 步:

  1. 命令追加(append):所有的寫命令會追加到 AOF 緩衝區中。
  2. 檔案寫入(write):將 AOF 緩衝區的資料寫入到 AOF 檔案中。這一步需要呼叫write函數(系統呼叫),write將資料寫入到了系統核心緩衝區之後直接返回了(延遲寫)。注意!!!此時並沒有同步到磁碟。
  3. 檔案同步(fsync):AOF 緩衝區根據對應的持久化方式( fsync 策略)向硬碟做同步操作。這一步需要呼叫 fsync 函數(系統呼叫), fsync 針對單個檔案操作,對其進行強制硬碟同步,fsync 將阻塞直到寫入磁碟完成後返回,保證了資料持久化。
  4. 檔案重寫(rewrite):隨著 AOF 檔案越來越大,需要定期對 AOF 檔案進行重寫,達到壓縮的目的。
  5. 重啟載入(load):當 Redis 重啟時,可以載入 AOF 檔案進行資料恢復。

Linux 系統直接提供了一些函數用於對檔案和裝置進行存取和控制,這些函數被稱為 系統呼叫(syscall)

這裡對上面提到的一些 Linux 系統呼叫再做一遍解釋:

  • write:寫入系統核心緩衝區之後直接返回(僅僅是寫到緩衝區),不會立即同步到硬碟。雖然提高了效率,但也帶來了資料丟失的風險。同步硬碟操作通常依賴於系統排程機制,Linux 核心通常為 30s 同步一次,具體值取決於寫出的資料量和 I/O 緩衝區的狀態。
  • fsyncfsync用於強制刷新系統核心緩衝區(同步到到磁碟),確保寫磁碟操作結束才會返回。

AOF 工作流程圖如下:

AOF 持久化方式有哪些?

在 Redis 的組態檔中存在三種不同的 AOF 持久化方式( fsync策略),它們分別是:

  1. appendfsync always:主執行緒呼叫 write 執行寫操作後,後臺執行緒( aof_fsync 執行緒)立即會呼叫 fsync 函數同步 AOF 檔案(刷盤),fsync 完成後執行緒返回,這樣會嚴重降低 Redis 的效能(write + fsync)。
  2. appendfsync everysec:主執行緒呼叫 write 執行寫操作後立即返回,由後臺執行緒( aof_fsync 執行緒)每秒鐘呼叫 fsync 函數(系統呼叫)同步一次 AOF 檔案(write+fsyncfsync間隔為 1 秒)
  3. appendfsync no:主執行緒呼叫 write 執行寫操作後立即返回,讓作業系統決定何時進行同步,Linux 下一般為 30 秒一次(write但不fsyncfsync 的時機由作業系統決定)。

可以看出:這 3 種持久化方式的主要區別在於 fsync 同步 AOF 檔案的時機(刷盤)

為了兼顧資料和寫入效能,可以考慮 appendfsync everysec 選項 ,讓 Redis 每秒同步一次 AOF 檔案,Redis 效能受到的影響較小。而且這樣即使出現系統崩潰,使用者最多隻會丟失一秒之內產生的資料。當硬碟忙於執行寫入操作的時候,Redis 還會優雅的放慢自己的速度以便適應硬碟的最大寫入速度。

從 Redis 7.0.0 開始,Redis 使用了 Multi Part AOF 機制。顧名思義,Multi Part AOF 就是將原來的單個 AOF 檔案拆分成多個 AOF 檔案。在 Multi Part AOF 中,AOF 檔案被分為三種型別,分別為:

  • BASE:表示基礎 AOF 檔案,它一般由子程序通過重寫產生,該檔案最多隻有一個。
  • INCR:表示增量 AOF 檔案,它一般會在 AOFRW 開始執行時被建立,該檔案可能存在多個。
  • HISTORY:表示歷史 AOF 檔案,它由 BASE 和 INCR AOF 變化而來,每次 AOFRW 成功完成時,本次 AOFRW 之前對應的 BASE 和 INCR AOF 都將變為 HISTORY,HISTORY 型別的 AOF 會被 Redis 自動刪除。

Multi Part AOF 不是重點,瞭解即可,詳細介紹可以看看阿里開發者的Redis 7.0 Multi Part AOF 的設計和實現 這篇文章。

相關 issueRedis 的 AOF 方式 #783

AOF 為什麼是在執行完命令之後記錄紀錄檔?

關係型資料庫(如 MySQL)通常都是執行命令之前記錄紀錄檔(方便故障恢復),而 Redis AOF 持久化機制是在執行完命令之後再記錄紀錄檔。

為什麼是在執行完命令之後記錄紀錄檔呢?

  • 避免額外的檢查開銷,AOF 記錄紀錄檔不會對命令進行語法檢查;
  • 在命令執行完之後再記錄,不會阻塞當前的命令執行。

這樣也帶來了風險(我在前面介紹 AOF 持久化的時候也提到過):

  • 如果剛執行完命令 Redis 就宕機會導致對應的修改丟失;
  • 可能會阻塞後續其他命令的執行(AOF 記錄紀錄檔是在 Redis 主執行緒中進行的)。

AOF 重寫了解嗎?

當 AOF 變得太大時,Redis 能夠在後臺自動重寫 AOF 產生一個新的 AOF 檔案,這個新的 AOF 檔案和原有的 AOF 檔案所儲存的資料庫狀態一樣,但體積更小。

AOF 重寫(rewrite) 是一個有歧義的名字,該功能是通過讀取資料庫中的鍵值對來實現的,程式無須對現有 AOF 檔案進行任何讀入、分析或者寫入操作。

由於 AOF 重寫會進行大量的寫入操作,為了避免對 Redis 正常處理命令請求造成影響,Redis 將 AOF 重寫程式放到子程序裡執行。

AOF 檔案重寫期間,Redis 還會維護一個 AOF 重寫緩衝區,該緩衝區會在子程序建立新 AOF 檔案期間,記錄伺服器執行的所有寫命令。當子程序完成建立新 AOF 檔案的工作之後,伺服器會將重寫緩衝區中的所有內容追加到新 AOF 檔案的末尾,使得新的 AOF 檔案儲存的資料庫狀態與現有的資料庫狀態一致。最後,伺服器用新的 AOF 檔案替換舊的 AOF 檔案,以此來完成 AOF 檔案重寫操作。

開啟 AOF 重寫功能,可以呼叫 BGREWRITEAOF 命令手動執行,也可以設定下面兩個設定項,讓程式自動決定觸發時機:

  • auto-aof-rewrite-min-size:如果 AOF 檔案大小小於該值,則不會觸發 AOF 重寫。預設值為 64 MB;
  • auto-aof-rewrite-percentage:執行 AOF 重寫時,當前 AOF 大小(aof_current_size)和上一次重寫時 AOF 大小(aof_base_size)的比值。如果當前 AOF 檔案大小增加了這個百分比值,將觸發 AOF 重寫。將此值設定為 0 將禁用自動 AOF 重寫。預設值為 100。

Redis 7.0 版本之前,如果在重寫期間有寫入命令,AOF 可能會使用大量記憶體,重寫期間到達的所有寫入命令都會寫入磁碟兩次。

Redis 7.0 版本之後,AOF 重寫機制得到了優化改進。下面這段內容摘自阿里開發者的從 Redis7.0 釋出看 Redis 的過去與未來 這篇文章。

AOF 重寫期間的增量資料如何處理一直是個問題,在過去寫期間的增量資料需要在記憶體中保留,寫結束後再把這部分增量資料寫入新的 AOF 檔案中以保證資料完整性。可以看出來 AOF 寫會額外消耗記憶體和磁碟 IO,這也是 Redis AOF 寫的痛點,雖然之前也進行過多次改進但是資源消耗的本質問題一直沒有解決。

阿里雲的 Redis 企業版在最初也遇到了這個問題,在內部經過多次迭代開發,實現了 Multi-part AOF 機制來解決,同時也貢獻給了社群並隨此次 7.0 釋出。具體方法是採用 base(全量資料)+inc(增量資料)獨立檔案儲存的方式,徹底解決記憶體和 IO 資源的浪費,同時也支援對歷史 AOF 檔案的儲存管理,結合 AOF 檔案中的時間資訊還可以實現 PITR 按時間點恢復(阿里雲企業版 Tair 已支援),這進一步增強了 Redis 的資料可靠性,滿足使用者資料回檔等需求。

相關 issueRedis AOF 重寫描述不準確 #1439

AOF 校驗機制瞭解嗎?

AOF 校驗機制是 Redis 在啟動時對 AOF 檔案進行檢查,以判斷檔案是否完整,是否有損壞或者丟失的資料。這個機制的原理其實非常簡單,就是通過使用一種叫做 校驗和(checksum) 的數位來驗證 AOF 檔案。這個校驗和是通過對整個 AOF 檔案內容進行 CRC64 演演算法計算得出的數位。如果檔案內容發生了變化,那麼校驗和也會隨之改變。因此,Redis 在啟動時會比較計算出的校驗和與檔案末尾儲存的校驗和(計算的時候會把最後一行儲存校驗和的內容給忽略點),從而判斷 AOF 檔案是否完整。如果發現檔案有問題,Redis 就會拒絕啟動並提供相應的錯誤資訊。AOF 校驗機制十分簡單有效,可以提高 Redis 資料的可靠性。

類似地,RDB 檔案也有類似的校驗機制來保證 RDB 檔案的正確性,這裡就不重複進行介紹了。

Redis 4.0 對於持久化機制做了什麼優化?

由於 RDB 和 AOF 各有優勢,於是,Redis 4.0 開始支援 RDB 和 AOF 的混合持久化(預設關閉,可以通過設定項 aof-use-rdb-preamble 開啟)。

如果把混合持久化開啟,AOF 重寫的時候就直接把 RDB 的內容寫到 AOF 檔案開頭。這樣做的好處是可以結合 RDB 和 AOF 的優點, 快速載入同時避免丟失過多的資料。當然缺點也是有的, AOF 裡面的 RDB 部分是壓縮格式不再是 AOF 格式,可讀性較差。

官方檔案地址:https://redis.io/topics/persistence

如何選擇 RDB 和 AOF?

關於 RDB 和 AOF 的優缺點,官網上面也給了比較詳細的說明Redis persistence,這裡結合自己的理解簡單總結一下。

RDB 比 AOF 優秀的地方

  • RDB 檔案儲存的內容是經過壓縮的二進位制資料, 儲存著某個時間點的資料集,檔案很小,適合做資料的備份,災難恢復。AOF 檔案儲存的是每一次寫命令,類似於 MySQL 的 binlog 紀錄檔,通常會比 RDB 檔案大很多。當 AOF 變得太大時,Redis 能夠在後臺自動重寫 AOF。新的 AOF 檔案和原有的 AOF 檔案所儲存的資料庫狀態一樣,但體積更小。不過, Redis 7.0 版本之前,如果在重寫期間有寫入命令,AOF 可能會使用大量記憶體,重寫期間到達的所有寫入命令都會寫入磁碟兩次。
  • 使用 RDB 檔案恢復資料,直接解析還原資料即可,不需要一條一條地執行命令,速度非常快。而 AOF 則需要依次執行每個寫命令,速度非常慢。也就是說,與 AOF 相比,恢復巨量資料集的時候,RDB 速度更快。

AOF 比 RDB 優秀的地方

  • RDB 的資料安全性不如 AOF,沒有辦法實時或者秒級持久化資料。生成 RDB 檔案的過程是比較繁重的, 雖然 BGSAVE 子程序寫入 RDB 檔案的工作不會阻塞主執行緒,但會對機器的 CPU 資源和記憶體資源產生影響,嚴重的情況下甚至會直接把 Redis 服務幹宕機。AOF 支援秒級資料丟失(取決 fsync 策略,如果是 everysec,最多丟失 1 秒的資料),僅僅是追加命令到 AOF 檔案,操作輕量。
  • RDB 檔案是以特定的二進位制格式儲存的,並且在 Redis 版本演進中有多個版本的 RDB,所以存在老版本的 Redis 服務不相容新版本的 RDB 格式的問題。
  • AOF 以一種易於理解和解析的格式包含所有操作的紀錄檔。你可以輕鬆地匯出 AOF 檔案進行分析,你也可以直接操作 AOF 檔案來解決一些問題。比如,如果執行FLUSHALL命令意外地重新整理了所有內容後,只要 AOF 檔案沒有被重寫,刪除最新命令並重啟即可恢復之前的狀態。

綜上

  • Redis 儲存的資料丟失一些也沒什麼影響的話,可以選擇使用 RDB。
  • 不建議單獨使用 AOF,因為時不時地建立一個 RDB 快照可以進行資料庫備份、更快的重啟以及解決 AOF 引擎錯誤。
  • 如果儲存的資料要求安全性比較高的話,建議同時開啟 RDB 和 AOF 持久化或者開啟 RDB 和 AOF 混合持久化。

快取原創文章推薦