天天看點

美團面試:當機了,Redis 如何避免資料丢失?

作者:老誠不bug

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

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

内容概覽:

美團面試:當機了,Redis 如何避免資料丢失?

下面是正文。

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

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

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

官方文檔位址:https://redis.io/topics/persistence 。

美團面試:當機了,Redis 如何避免資料丢失?

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 緩沖區的狀态。
  • fsync:fsync用于強制重新整理系統核心緩沖區(同步到到磁盤),確定寫磁盤操作結束才會傳回。

AOF 工作流程圖如下:

美團面試:當機了,Redis 如何避免資料丢失?

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+fsync,fsync間隔為 1 秒)
  3. 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 的設計和實作[1] 這篇文章。

相關 issue:Redis 的 AOF 方式 #783[2]。

AOF 為什麼是在執行完指令之後記錄日志?

關系型資料庫(如 MySQL)通常都是執行指令之前記錄日志(友善故障恢複),而 Redis AOF 持久化機制是在執行完指令之後再記錄日志。

美團面試:當機了,Redis 如何避免資料丢失?

AOF 記錄日志過程

為什麼是在執行完指令之後記錄日志呢?

  • 避免額外的檢查開銷,AOF 記錄日志不會對指令進行文法檢查;
  • 在指令執行完之後再記錄,不會阻塞目前的指令執行。

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

  • 如果剛執行完指令 Redis 就當機會導緻對應的修改丢失;
  • 可能會阻塞後續其他指令的執行(AOF 記錄日志是在 Redis 主線程中進行的)。

AOF 重寫了解嗎?

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

美團面試:當機了,Redis 如何避免資料丢失?

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[3]。

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

美團面試:當機了,Redis 如何避免資料丢失?

如何選擇 RDB 和 AOF?

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

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 混合持久化。

參考資料

[1]Redis 7.0 Multi Part AOF 的設計和實作: https://zhuanlan.zhihu.com/p/467217082

[2]Redis 的 AOF 方式 #783: https://github.com/Snailclimb/JavaGuide/issues/783

[3]Redis AOF 重寫描述不準确 #1439: https://github.com/Snailclimb/JavaGuide/issues/1439

[4]Redis persistence: https://redis.io/docs/manual/persistence/

來源:https://mp.weixin.qq.com/s/Rt9Xf7W9zqzqhqQssyySuA

繼續閱讀