Redis系列:RDB記憶體快照提供持久化能力

Hello-Brand發表於2024-03-07

Redis24篇集合

1 介紹

從上一篇的 《深刻理解高效能Redis的本質》 中可以知道, 我們經常在資料庫層上加一層快取(如Redis),來保證資料的訪問效率。
這樣效能確實也有了大幅度的提升,因為從記憶體中取數遠比從磁碟中快的多,但是本身Redis也是一層服務,也存在當機、故障的可能性。
一旦服務掛起,可能生產的後果包括如下幾方面:
1. Redis的資料是存在記憶體中的,所以一旦掛起,記憶體中的資料會全部丟失。
2. I/O從記憶體層級遷移到磁碟層級,效能極速下降。
3. 原本訪問快取的請求會透過快取層直接投向資料庫,給資料庫帶來極大的壓力,甚至導致雪崩。

所以,快取層崩潰產生的後果是災難的。為了避免當機和當機後的資料丟失, 為了保證資料的快速恢復,Redis提供了兩個持久化資料的能力,RDB Snapshot 和 AOF(Append Only FIle)日誌。本章我們先來看看RDB快照的使用。

2 什麼是RDB記憶體快照

大規模高併發的分散式場景,經常會遇到問題就是Redis掛起,導致訪問失敗,而所有的請求透過快取層投向資料庫,給資料庫造成極大的壓力,甚至雪崩。

而Redis的資料是儲存在快取記憶體中,即使我們重啟並且恢復使用,快取池依舊是空的,因為記憶體被釋放了。
重新建立快取的過程,對資料庫也是一個暴擊的過程,很可能會導致整個系統呼叫鏈的雪崩。參考我的這篇《架構與思維:一次快取雪崩的災難覆盤
所以更為穩妥的辦法是持久化到磁碟中,這樣哪怕重啟資料也不會消失。但是如果每次資料的變化(增、刪、改)都要寫記憶體並同時寫磁碟,這樣成本太高,記憶體+磁碟的持續資料同步,會讓 Redis 效能大大降低。而且還要保證原子性操作,避免記憶體和磁碟的資料不一致。

2.1 使用記憶體快照

為了避免實時寫入高頻操作磁碟帶來的負面效應。Redis提供了記憶體快照策略。
工作原理是,Redis在指定的時間間隔內,將記憶體中的資料集快照定格下來,寫入磁碟,並儲存在副本檔案中。當Redis重啟時,這些快照檔案會被自動讀取並恢復到記憶體中。打遊戲的同學可以想象存檔,下一次恢復遊戲,可以從存檔的地方讀取遊戲直接開始。
image
如上圖,將指定時間的Redis快取資料進行快照。當發生故障的時候,直接從最接近的時間點進行資料恢復(即21:10的故障按照21點的RDB快照進行恢復),直接將 RDB 檔案讀入記憶體完成恢復。

2.2 生成RDB策略

在Redis的RDB持久化方案中,提供了兩種模式來生成RDS檔案,分別是 SAVE 和 BGSAVE。雖然都是用於建立記憶體快照並儲存到磁碟的命令,但兩者在執行方式和影響上有明顯的區別。

SAVE命令會阻塞當前Redis伺服器程序,直到RDB檔案建立完畢。
在執行SAVE命令期間,Redis不能處理其他命令,阻塞主程序,這會導致伺服器無法響應其他請求,直到RDB過程完成為止。因此,當資料量較大時,使用SAVE命令可能會對Redis的效能產生較大影響。

BGSAVE命令則會在後臺非同步進行快照操作,同時Redis還可以繼續處理客戶端的請求。
BGSAVE命令透過fork一個子程序來完成持久化任務,這樣主程序就不會被阻塞,從而保證了Redis的高可用性。但是,由於需要fork一個子程序,BGSAVE命令可能會消耗更多的記憶體資源。

2.2.1 SAVE模式

save模式是主程序執行,非常不建議使用主程序執行的方式,在筆者的 《深刻理解高效能Redis的本質》 一文中,
我們介紹了它的主操作都是在單執行緒模型上完成的。所以 RDB 檔案生成會影響主執行緒的網路I/O和鍵值對讀寫,導致客戶端正常操作被阻塞,所以應該儘量避免。

2.2.2 BGSAVE模式

bgsave是後臺非同步執行,透過呼叫glibc函式建立一個子程序專門用於寫入RDB檔案,從而避免了主執行緒的阻塞。當執行BGSAVE命令時,Redis會繼續處理其他客戶端請求(比如Get、Set等),而子程序會在後臺完成RDB檔案的生成。這是Redis RDB檔案生成的預設配置,也是推薦的方式。
image
上圖執行流程如下:

  1. 執行bgsave命令,Redis主程序判斷當前是否存在正在執行的RDB/AOF子程序,若果存在則bgsave命令直接返回。
  2. 主程序執行fork操作建立子程序,fork操作過程中父程序會阻塞(建立子程序),透過info stats命令檢視latest_fork_usec選項,可以獲取最近一個fork操作的耗時,單位為微秒
  3. 父程序fork完成後,bgsave命令返回Background saving started資訊,之後的操作都是非同步的了,不再阻塞主程序,Client的Get、Set等操作依然可以執行。
  4. fork子程序的做法是透過呼叫glibc函式進行建立的,這步驟跟第2點對齊,都是會有短暫的阻塞。
  5. 子程序建立RDB檔案,在主程序記憶體中生成臨時快照檔案,完成後對原有檔案進行原子替換。執行lastsave命令可以獲取最後一次生成RDB的時間,對應rdb_last_save_time選項。
  6. 子程序傳送訊號給主程序表示完成,主程序接受到資訊並更新統計記錄。

以上整個過程保證了快照的完整性,也允許主程序同時對資料進行修改,避免了對正常業務的影響。

2.2.3 避免過頻的全量Snapshot

雖然說Redis 使用 bgsave 函式 fork 子程序在後臺完成記憶體中的資料做快照,並不阻塞父程序繼續處理客戶端的操作。
但過頻執行全量資料快照,依然會導致嚴重的效能開銷,主要如下:

  1. 頻繁生成 RDB 檔案寫入磁碟,磁碟空間佔用大,IO壓力大,也會降低效率。
  2. fork 出來的 bgsave 子程序因為共享主執行緒的資源,一定程度上會影響主執行緒的執行效能。

2.3 總結

快照的恢復速度快,但是生成 RDB 檔案的頻率需要把握一個度,頻率過低快照間隔資料較大,丟失的資料就會比較多;頻率太快,又會消耗額外開銷,降低Redis效能。
RDB記憶體快照優缺點如下:
優點:

  • RDB以一種二進位制格式+資料壓縮的方式寫磁碟,檔案輕量。
  • 資料恢復速度快,用於災難恢復的場景,載入 RDB 恢復資料遠快於 AOF 方式。

缺點:

  • 無法做到實時持久化,每次都要建立子程序, 頻繁操作成本過高
  • 儲存後的二進位制檔案, 存在老版本不相容新版本 rdb 檔案的問題
  • 資料恢復不完全,快照時間點和故障時間點之間必然有時間差、資料差

相關文章