Redis_RDB持久化之寫時復制技術的應用


背景:

最近生產環境中某個Set的Redis集群經常出現短暫的內存降低現象,經過查看日志是因為在RDB持久化所造成的內存突降(日志中:RDB: 4929 MB of memory used by copy-on-write  ),其根本原理是RDB持久化的過程中,Redis借助操作系統提供的寫時復制技術(Copy-On-Write,COW),在執行bgsave(snapshot)快照的同時,會間接消耗額外的內存。

 

1.RDB持久化原理

        RDB是一次的全量備份,即周期性的把Redis當前內存中的全量數據寫入到一個快照文件中。Redis是單線程程序,這個線程要同時負責多個客戶端的讀寫請求,還要負責周期性的把當前內存中的數據寫到快照文件中RDB中,數據寫到RDB文件是IO操作,IO操作會嚴重影響Redis的性能,甚至在持久化的過程中,讀寫請求會阻塞,為了解決這些問題,Redis需要同時進行讀寫請求和持久化操作,這樣又會導致另外的問題:持久化的過程中,內存中的數據還在改變,假如Redis正在進行持久化一個大的數據結構,在這個過程中客戶端發送一個刪除請求,把這個大的數據結構刪掉了,這時候持久化的動作還沒有完成,那么Redis該怎么辦呢?

  於是Redis使用操作系統的多進程寫時復制(Copy On Write)機制來實現快照的持久化,在持久化過程中調用glibc(Linux下的C函數庫)的函數fork()產生一個子進程,快照持久化完全交給子進程來處理,父進程繼續處理客戶端的讀寫請求。子進程剛剛產生時,和父進程共享內存里面的代碼段和數據段,也就是說,父子進程的虛擬空間不同,但其對應的物理空間(內存區)是同一個。這是Linux操作系統的機制,為了節約內存資源,所以盡可能讓父子進程共享內存,這樣在進程分離的一瞬間,內存的增長幾乎沒有明顯變化。

(如果fork操作本身耗時過長,將導致主進程阻塞,可以執行info stats命令獲取到latest_fork_usec指標,表示最近一次fork操作的耗時,若操作1s則需優化)

寫時復制技術:

       如果主線程收到的客戶端的讀寫請求,需要修改某塊數據,那么這塊數據就會被復制一份到內存,生成該數據的副本,主進程在該副本上進行修改操作。所以即使對某個數據進行了修改,Redis持久化到RDB中的數據也是未修改的數據,這也是把RDB文件稱為"快照"文件的原因,子進程所看到的數據在它被創建的一瞬間就固定下來了,父進程修改的某個數據只是該數據的復制品。這里再深入一點,Redis內存中的全量數據由一個個的"數據段頁面"組成,每個數據段頁面的大小為4K,客戶端要修改的數據在哪個頁面中,就會復制一份這個頁面到內存中,這個復制的過程稱為"頁面分離",在持久化過程中,隨着分離出的頁面越來越多,內存就會持續增長,但是不會超過原內存的2倍,因為在一次持久化的過程中,幾乎不會出現所有的頁面都會分離的情況,讀寫請求針對的只是原數據中的小部分,大部分Redis數據還是"冷數據"。

       正因為修改的部分數據會被額外的復制一份,所以會占用額外的內存,當在進行RDB持久化操作的過程中,與此同時如果持續往redis中寫入的數據量越多,就會導致占用的額外內存消耗越大。

 

那么在此期間寫入的數據最終去哪了呢? 

寫入的數據還是存在了內存當中,並沒有寫入當前的持久化文件中,等到下次進行RDB持久化時才會把 ” 寫入的數據 ” 落盤到RDB文件中。

 

bgsave:fork出的子進程開始根據父進程內存數據生成臨時的快照文件,然后替換原文件。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 這里解釋一下幾個跟 RDB 相關的參數:

  • rdb_changes_since_last_save:自上次 RDB 后,Redis 數據的改動條數
  • rdb_bgsave_in_progress:bgsave 是否在進行中,0 否,1 是
  • rdb_last_save_time:上次 bgsave 的時間戳
  • rdb_last_bgsave_status:上次 bgsave 的狀態
  • rdb_last_bgsave_time_sec:上次 bgsave 的持續時間
  • rdb_current_bgsave_time_sec:正在執行的 bgsave 耗時,如果沒有正在執行的,則為 -1
  • rdb_last_cow_size:上次 RDB 過程中父進程與子進程相比執行了多少修改

根據 rdb_bgsave_in_progress 這一項為 0,可以判斷在執行 info Persistence 命令時,bgsave 已經執行完成了。除了通過命令的方式觸發 RDB 持久化之外,Redis 內部還有自動觸發 RDB 的機制。比如以下場景:

  • 配置文件中增加了類似 "save m n" 的配置,表示 m 秒內有 n 次修改則自動觸發 bgsave。
  • 新建立 Redis 主從復制時,主節點會執行一次 bgsave 保存 RDB 文件到本地,然后發送給從節點。
  • 執行 shutdown 時,如果沒有開啟 AOF 則自動執行 bgsave。
  • 哨兵模式發生主從切換時,會主動進行一次初始化操作,執行bgsave保存RDB文件到本地。

 

2 頻繁執行全量快照的影響

 

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

  • 頻繁將全量數據寫入磁盤,會給磁盤帶來很大壓力,可能出現前面的沒做完,后面的又開始了。導致惡性循環。
  • bgsave 子進程需要通過 fork 操作從主線程創建出來,雖然,子進程在創建后不在會阻塞主線程,但是,fork這個創建過程本身會阻塞主線程,而且主線程內存越大,阻塞時間越長。

 

3 運維技巧

 

 3.1 RDB 所在分區磁盤滿了怎么辦?

當遇到 RDB 所在分區磁盤滿了,可以臨時修改 RDB 路徑,操作如下:

 

3.2 開啟 RDB 壓縮

Redis 支持對 RDB 進行壓縮,參數為 rdbcompression,設置為 yes 表示開啟(默認開啟的)。壓縮不但可以節省磁盤空間,在創建主從時,也能更快的將全量備份傳給從實例,因此建議開啟壓縮功能。

3.3 RDB 文件損壞檢測

當發現 Reids RDB 文件損壞時,可以使用 redis-check-rdb 進行檢測,用法如下:

 RDB looks OK! 說明rdb文件沒有錯誤。

3.4 單機多實例的 RDB 備份

有些情況,我們會在單台服務器上部署多個 Redis 實例,但是使用配置文件中增加 save 的方式又怕幾個實例 RDB 時間沖突,從而影響落盤速度。這種情況,可以使用腳本結合定時任務觸發 bgsave 進行 RDB 備份。這樣,同機器不同實例的 RDB 備份時間可以自定義錯開,防止 IO 跑滿帶來的問題。(注意一定要設置好持久化的目錄,防止多個實例共用同一目錄)

 

4 備份建議

那么 Redis 究竟怎么備份更好呢?RDB 盡管恢復會快很多,但是可靠性比 AOF 低,但是如果只使用 AOF,又會存在恢復慢的問題,因此,Redis 4.0 提出了混合使用 AOF 日志和內存快照的方法。因此對於 Redis 的備份,建議如下:

  • 數據不能丟失時,內存快照和 AOF 的混合使用是一個很好的選擇;aof-use-rdb-preamble 配置設置為 yes ;(Redis5.0版本以后默認是開啟的)
  • 如果允許分鍾級別的數據丟失,可以只使用 RDB;
  • 如果只用 AOF ,優先使用 everysec 的配置選項,因為其介於可靠性和性能之間;

當然,如果有從實例,也優先考慮在從實例上進行備份。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM