本文已經收錄進 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 步:
- 命令追加(append):所有的寫命令會追加到 AOF 緩衝區中。
- 文件寫入(write):將 AOF 緩衝區的數據寫入到 AOF 文件中。這一步需要調用
write
函數(系統調用),write
將數據寫入到了系統內核緩衝區之後直接返回了(延遲寫)。注意!!!此時並沒有同步到磁盤。 - 文件同步(fsync):AOF 緩衝區根據對應的持久化方式(
fsync
策略)向硬盤做同步操作。這一步需要調用fsync
函數(系統調用),fsync
針對單個文件操作,對其進行強制硬盤同步,fsync
將阻塞直到寫入磁盤完成後返回,保證了數據持久化。 - 文件重寫(rewrite):隨着 AOF 文件越來越大,需要定期對 AOF 文件進行重寫,達到壓縮的目的。
- 重啓加載(load):當 Redis 重啓時,可以加載 AOF 文件進行數據恢復。
Linux 系統直接提供了一些函數用於對文件和設備進行訪問和控制,這些函數被稱爲 系統調用(syscall)。
這裏對上面提到的一些 Linux 系統調用再做一遍解釋:
write
:寫入系統內核緩衝區之後直接返回(僅僅是寫到緩衝區),不會立即同步到硬盤。雖然提高了效率,但也帶來了數據丟失的風險。同步硬盤操作通常依賴於系統調度機制,Linux 內核通常爲 30s 同步一次,具體值取決於寫出的數據量和 I/O 緩衝區的狀態。fsync
:fsync
用於強制刷新系統內核緩衝區(同步到到磁盤),確保寫磁盤操作結束纔會返回。
AOF 工作流程圖如下:
AOF 持久化方式有哪些?
在 Redis 的配置文件中存在三種不同的 AOF 持久化方式( fsync
策略),它們分別是:
appendfsync always
:主線程調用write
執行寫操作後,後臺線程(aof_fsync
線程)立即會調用fsync
函數同步 AOF 文件(刷盤),fsync
完成後線程返回,這樣會嚴重降低 Redis 的性能(write
+fsync
)。appendfsync everysec
:主線程調用write
執行寫操作後立即返回,由後臺線程(aof_fsync
線程)每秒鐘調用fsync
函數(系統調用)同步一次 AOF 文件(write
+fsync
,fsync
間隔爲 1 秒)appendfsync no
:主線程調用write
執行寫操作後立即返回,讓操作系統決定何時進行同步,Linux 下一般爲 30 秒一次(write
但不fsync
,fsync
的時機由操作系統決定)。
可以看出:這 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 的設計和實現 這篇文章。
相關 issue:Redis 的 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 的數據可靠性,滿足用戶數據回檔等需求。
相關 issue:Redis 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 混合持久化。
緩存原創文章推薦: