­

Redis基礎篇(四)持久化:記憶體快照(RDB)

AOF好處是每次執行只需要記錄操作命令,記錄量不大。但在故障恢復時,需要逐一執行AOF的操作命令,如果日誌很大,恢復就很慢。

今天學習另一種持久化方式:記憶體快照。記憶體快照,是Redis某一時刻的狀態,以文件的形式保存在磁碟上。這個快照文件就稱為RDB文件,其中RDB就是Redis Database的縮寫。

當故障恢復時,只要把RDB文件讀入記憶體即可,恢復速度很快。但是記憶體快照並不是最優選項,為什麼呢?

我們還需要考慮兩個問題:

  • 對哪些數據做快照?這關係到快照的執行效率;
  • 做快照時,數據還能被增刪改嗎?這關係到Redis是否被阻塞,能否同時正常處理請求。

對哪些數據做快照?

Redis的數據都在記憶體中,為了提供所有數據的可靠性保證,它執行的是全量快照。

Redis提供兩個命令生成RDB文件:

  • save:在主執行緒中執行,但會導致阻塞;
  • bgsave:創建一個子進程,專門用於寫入RDB文件,避免了主執行緒的阻塞。也是Redis RDB文件生成的默認配置。

做快照時,數據還能被增刪改嗎?

如果快照執行期間數據不能被修改,是會有潛在問題的。假如做快照的20s時間裡,如果數據不能被修改,Redis就不能處理對這些數據的寫操作,那無疑就會給業務服務造成巨大影響。

Redis藉助作業系統提供的寫時複製技術(Copy-On-Write,COW),在執行快照的同時,正常處理寫操作。

bgsave流程圖

bgsave子進程是由主執行緒fork生成的,可以共享主執行緒的所有記憶體數據。

當主執行緒要修改數據時,通過COW複製一份副本出來給bgsave子進程。

這既保證快照的完整性,也允許主執行緒同時對數據進行修改,避免了對正常業務的影響。

可以每秒做一次快照嗎?

如果記憶體快照每隔幾分鐘執行一次,這樣就可能出現丟失數據的風險。那麼是否可以每秒做一次快照嗎?

如果頻繁地執行全量快照,會帶來兩方面開銷:

  • 一是頻繁將全量數據寫入磁碟,會給磁碟帶來很大壓力。
  • 二是bgsave子進程需要從主執行緒fork出來,fork創建過程本身會阻塞主執行緒,而且主執行緒的記憶體越大,阻塞時間越長。

既然頻繁執行全量快照不行,那還有什麼其他好方法嗎?

第一個方法是做增量快照

在第一次做完全量快照後,T1和T2時刻如果再做快照,只需要將被修改的數據寫入快照文件就行了。如下圖所示:

image

第二個方法是Redis 4.0提出一個混合使用AOF日誌和RDB快照的方法:RDB快照以一定的頻率執行,在兩次快照之間,使用AOF日誌記錄這期間的所有命令操作。

T1和T2時刻的修改,用AOF日誌記錄,等到第二次做全量快照時,就可以清空AOF日誌,因為此時的修改都已經記錄到快照中了。如下圖所示:

image

拓展

使用一個2核CPU、4GB記憶體、500GB磁碟的雲主機運行Redis,Redis資料庫的數據量大小差不多是2GB,使用了RDB做持久化保證。當時Redis的運行負載以修改操作為主,寫讀比例差不多在8:2左右,也就是說,如果有100個請求,80個請求執行的是修改操作。在這個場景下,用RDB做持久化有什麼風險嗎?

主要有兩方面風險:記憶體資源風險CPU資源風險

記憶體資源風險

Redis fork子進程做RDB持久化,由於寫的比例為80%,那麼在持久化過程中,「寫時複製」會重新分配整個實例80%的記憶體副本,大約需要重新分配1.6GB記憶體空間,這樣整個系統的記憶體使用接近飽和。

如果此時父進程又有大量新key寫入,很快機器記憶體就會被吃光,如果機器開啟了Swap機制,那麼Redis會有一部分數據被換到磁碟上。

當Redis訪問這部分在磁碟上的數據時,性能會急劇下降,已經達不到高性能的標準。

如果沒有開啟Swap,會直接觸發OOM,父子進程會面臨被系統kill掉的風險。

CPU資源風險

雖然子進程在做RDB持久化,但生成RDB快照過程會消耗大量的CPU資源。

雖然Redis處理請求是單執行緒的,但Redis Server還有其他執行緒在後台工作,例如AOF每秒刷盤、非同步關閉文件描述符這些操作。

由於機器只有2核CPU,這也就是意味著父進程佔用了超過一半的CPU資源。

此時子進程做RDB持久化,可能會產生CPU競爭,導致的結果是父進程處理請求延遲增大,子進程生成RDB快照的時間也會變長,整個Redis Server性能下降。

如果Redis進程綁定了CPU,那麼子進程會繼續父進程的CPU親和性屬性,子進程必然會與父進程爭奪同一個CPU資源,整個Redis Server的性能必然會受到影響。

因此如果Redis需要開啟定時RDB和AOF重寫,進程一定不要綁定CPU。

參考資料