本文已經收錄進 JavaGuide(「Java學習+面試指南」一份涵蓋大部分 Java 程式設計師所需要掌握的核心知識。)
Redis 持久化機制屬於後端面試超高頻的面試知識點,老生常談了,需要重點花時間掌握。即使不是準備面試,日常開發也是需要經常用到的。
最近抽空對之前寫的 Redis 持久化機制進行了大幅完善,圖文並茂,清晰易懂。分享一下,希望對你有幫助!
內容概覽:
使用快取的時候,我們經常需要對記憶體中的資料進行持久化也就是將記憶體中的資料寫入到硬碟中。大部分原因是為了之後重用資料(比如重啟機器、機器故障之後恢復資料),或者是為了做資料同步(比如 Redis 叢集的主從節點通過 RDB 檔案同步資料)。
Redis 不同於 Memcached 的很重要一點就是,Redis 支援持久化,而且支援 3 種持久化方式:
官方檔案地址:https://redis.io/topics/persistence 。
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命令建立快照。
Redis 提供了兩個命令來生成 RDB 快照檔案:
save
: 同步儲存操作,會阻塞 Redis 主執行緒;bgsave
: fork 出一個子程序,子程序執行,不會阻塞 Redis 主執行緒,預設選項。這裡說 Redis 主執行緒而不是主程序的主要是因為 Redis 啟動之後主要是通過單執行緒的方式完成主要的工作。如果你想將其描述為 Redis 主程序,也沒毛病。
與快照持久化相比,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 持久化功能的實現可以簡單分為 5 步:
write
函數(系統呼叫),write
將資料寫入到了系統核心緩衝區之後直接返回了(延遲寫)。注意!!!此時並沒有同步到磁碟。fsync
策略)向硬碟做同步操作。這一步需要呼叫 fsync
函數(系統呼叫), fsync
針對單個檔案操作,對其進行強制硬碟同步,fsync
將阻塞直到寫入磁碟完成後返回,保證了資料持久化。Linux 系統直接提供了一些函數用於對檔案和裝置進行存取和控制,這些函數被稱為 系統呼叫(syscall)。
這裡對上面提到的一些 Linux 系統呼叫再做一遍解釋:
write
:寫入系統核心緩衝區之後直接返回(僅僅是寫到緩衝區),不會立即同步到硬碟。雖然提高了效率,但也帶來了資料丟失的風險。同步硬碟操作通常依賴於系統排程機制,Linux 核心通常為 30s 同步一次,具體值取決於寫出的資料量和 I/O 緩衝區的狀態。fsync
:fsync
用於強制刷新系統核心緩衝區(同步到到磁碟),確保寫磁碟操作結束才會返回。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 檔案被分為三種型別,分別為:
Multi Part AOF 不是重點,瞭解即可,詳細介紹可以看看阿里開發者的Redis 7.0 Multi Part AOF 的設計和實現 這篇文章。
相關 issue:Redis 的 AOF 方式 #783。
關係型資料庫(如 MySQL)通常都是執行命令之前記錄紀錄檔(方便故障恢復),而 Redis AOF 持久化機制是在執行完命令之後再記錄紀錄檔。
為什麼是在執行完命令之後記錄紀錄檔呢?
這樣也帶來了風險(我在前面介紹 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 校驗機制是 Redis 在啟動時對 AOF 檔案進行檢查,以判斷檔案是否完整,是否有損壞或者丟失的資料。這個機制的原理其實非常簡單,就是通過使用一種叫做 校驗和(checksum) 的數位來驗證 AOF 檔案。這個校驗和是通過對整個 AOF 檔案內容進行 CRC64 演演算法計算得出的數位。如果檔案內容發生了變化,那麼校驗和也會隨之改變。因此,Redis 在啟動時會比較計算出的校驗和與檔案末尾儲存的校驗和(計算的時候會把最後一行儲存校驗和的內容給忽略點),從而判斷 AOF 檔案是否完整。如果發現檔案有問題,Redis 就會拒絕啟動並提供相應的錯誤資訊。AOF 校驗機制十分簡單有效,可以提高 Redis 資料的可靠性。
類似地,RDB 檔案也有類似的校驗機制來保證 RDB 檔案的正確性,這裡就不重複進行介紹了。
由於 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 的優缺點,官網上面也給了比較詳細的說明Redis persistence,這裡結合自己的理解簡單總結一下。
RDB 比 AOF 優秀的地方:
AOF 比 RDB 優秀的地方:
FLUSHALL
命令意外地重新整理了所有內容後,只要 AOF 檔案沒有被重寫,刪除最新命令並重啟即可恢復之前的狀態。綜上:
快取原創文章推薦: