淺談Redis中的RDB快照

一、概述

所謂的快照,就是記錄某一個瞬間東西,比如當我們給風景拍照時,那一個瞬間的畫面和信息就記錄到瞭一張照片。

所以,RDB 快照就是記錄某一個瞬間的內存數據,記錄的是實際數據,而 AOF 文件記錄的是命令操作的日志,而不是實際的數據。

因此在 Redis 恢復數據時, RDB 恢復數據的效率會比 AOF 快些,因為直接將 RDB 文件讀入內存就可以瞭,不需要像 AOF 那樣還需要額外執行操作命令的步驟才能恢復數據。

接下來,就來具體聊聊 RDB 快照 。

二、快照怎麼用?

要熟悉一個東西,先看看怎麼用是比較好的方式。

Redis 提供瞭兩個命令來生成 RDB 文件,分別是 savebgsave,他們的區別就在於是否在「主線程」裡執行:

  • 執行瞭 save 命令,就會在主線程生成 RDB 文件,由於和執行操作命令在同一個線程,所以如果寫入 RDB 文件的時間太長,會阻塞主線程;
  • 執行瞭 bgsava 命令,會創建一個子進程來生成 RDB 文件,這樣可以避免主線程的阻塞;

RDB 文件的加載工作是在服務器啟動時自動執行的,Redis 並沒有提供專門用於加載 RDB 文件的命令。

Redis 還可以通過配置文件的選項來實現每隔一段時間自動執行一次 bgsava 命令,默認會提供以下配置:

save 900 1

save 300 10

save 60 10000

別看選項名叫 sava,實際上執行的是 bgsava 命令,也就是會創建子進程來生成 RDB 快照文件。

隻要滿足上面條件的任意一個,就會執行 bgsava,它們的意思分別是:

  • 900 秒之內,對數據庫進行瞭至少 1 次修改;
  • 300 秒之內,對數據庫進行瞭至少 10 次修改;
  • 60 秒之內,對數據庫進行瞭至少 10000 次修改。

這裡提一點,Redis 的快照是全量快照,也就是說每次執行快照,都是把內存中的「所有數據」都記錄到磁盤中。

所以可以認為,執行快照是一個比較重的操作,如果頻率太頻繁,可能會對 Redis 性能產生影響。如果頻率太低,服務器故障時,丟失的數據會更多。

通常可能設置至少 5 分鐘才保存一次快照,這時如果 Redis 出現宕機等情況,則意味著最多可能丟失 5 分鐘數據。

這就是 RDB 快照的缺點,在服務器發生故障時,丟失的數據會比 AOF 持久化的方式更多,因為 RDB 快照是全量快照的方式,因此執行的頻率不能太頻繁,否則會影響 Redis 性能,而 AOF 日志可以以秒級的方式記錄操作命令,所以丟失的數據就相對更少。

三、執行 bgsava 快照時,數據能被修改嗎?

那問題來瞭,執行 bgsava 過程中,由於是交給子進程來構建 RDB 文件,主線程還是可以繼續工作的,此時主線程可以修改數據嗎?

如果不可以修改數據的話,那這樣性能一下就降低瞭很多。如果可以修改數據,又是如何做到到呢?

直接說結論吧,執行 bgsava 過程中,Redis 依然可以繼續處理操作命令的,也就是數據是能被修改的。

那具體如何做到到呢?關鍵的技術就在於寫時復制技術(Copy-On-Write, COW)。

執行 bgsava 命令的時候,會通過 fork() 創建子進程,此時子進程和父進程是共享同一片內存數據的,因為創建子進程的時候,會復制父進程的頁表,但是頁表指向的物理內存還是一個。

隻有在發生修改內存數據的情況時,物理內存才會被復制一份。

這樣的目的是為瞭減少創建子進程時的性能損耗,從而加快創建子進程的速度,畢竟創建子進程的過程中,是會阻塞主線程的。

所以,創建 bgsave 子進程後,由於共享父進程的所有內存數據,於是就可以直接讀取主線程裡的內存數據,並將數據寫入到 RDB 文件。

當主線程對這些共享的內存數據也都是隻讀操作,那麼,主線程和 bgsave 子進程相互不影響。

但是,如果主線程要修改共享數據裡的某一塊數據(比如鍵值對 A)時,就會發生寫時復制,於是這塊數據的物理內存就會被復制一份(鍵值對 A'),然後主線程在這個數據副本(鍵值對 A')進行修改操作。與此同時,bgsave 子進程可以繼續把原來的數據(鍵值對 A)寫入到 RDB 文件。

就是這樣,Redis 使用 bgsave 對當前內存中的所有數據做快照,這個操作是由 bgsave 子進程在後臺完成的,執行時不會阻塞主線程,這就使得主線程同時可以修改數據。

細心的同學,肯定發現瞭,bgsave 快照過程中,如果主線程修改瞭共享數據,發生瞭寫時復制後,RDB 快照保存的是原本的內存數據,而主線程剛修改的數據,是被辦法在這一時間寫入 RDB 文件的,隻能交由下一次的 bgsave 快照。

所以 Redis 在使用 bgsave 快照過程中,如果主線程修改瞭內存數據,不管是否是共享的內存數據,RDB 快照都無法寫入主線程剛修改的數據,因為此時主線程的內存數據和子線程的內存數據已經分離瞭,子線程寫入到 RDB 文件的內存數據隻能是原本的內存數據。

如果系統恰好在 RDB 快照文件創建完畢後崩潰瞭,那麼 Redis 將會丟失主線程在快照期間修改的數據。

另外,寫時復制的時候會出現這麼個極端的情況。

在 Redis 執行 RDB 持久化期間,剛 fork 時,主進程和子進程共享同一物理內存,但是途中主進程處理瞭寫操作,修改瞭共享內存,於是當前被修改的數據的物理內存就會被復制一份。

那麼極端情況下,如果所有的共享內存都被修改,則此時的內存占用是原先的 2 倍。

所以,針對寫操作多的場景,我們要留意下快照過程中內存的變化,防止內存被占滿瞭。

四、RDB 和 AOF 合體

盡管 RDB 比 AOF 的數據恢復速度快,但是快照的頻率不好把握:

如果頻率太低,兩次快照間一旦服務器發生宕機,就可能會比較多的數據丟失; 如果頻率太高,頻繁寫入磁盤和創建子進程會帶來額外的性能開銷。

那有沒有什麼方法不僅有 RDB 恢復速度快的優點和,又有 AOF 丟失數據少的優點呢?

當然有,那就是將 RDB 和 AOF 合體使用,這個方法是在 Redis 4.0 提出的,該方法叫混合使用 AOF 日志和內存快照,也叫混合持久化。

如果想要開啟混合持久化功能,可以在 Redis 配置文件將下面這個配置項設置成 yes:

aof-use-rdb-preamble yes

混合持久化工作在 AOF 日志重寫過程。

當開啟瞭混合持久化時,在 AOF 重寫日志時,fork 出來的重寫子進程會先將與主線程共享的內存數據以 RDB 方式寫入到 AOF 文件,然後主線程處理的操作命令會被記錄在重寫緩沖區裡,重寫緩沖區裡的增量命令會以 AOF 方式寫入到 AOF 文件,寫入完成後通知主進程將新的含有 RDB 格式和 AOF 格式的 AOF 文件替換舊的的 AOF 文件。

也就是說,使用瞭混合持久化,AOF 文件的前半部分是 RDB 格式的全量數據,後半部分是 AOF 格式的增量數據。

這樣的好處在於,重啟 Redis 加載數據的時候,由於前半部分是 RDB 內容,這樣加載的時候速度會很快。

加載完 RDB 的內容後,才會加載後半部分的 AOF 內容,這裡的內容是 Redis 後臺子進程重寫 AOF 期間,主線程處理的操作命令,可以使得數據更少的丟失。

以上就是淺談Redis RDB快照的詳細內容,更多關於Redis RDB的資料請關註WalkonNet其它相關文章!

推薦閱讀: