圖解 Redis | 不多說了,這就是 RDB 快照

大家好,我是小林。

雖說 Redis 是內存數據庫。

但是它爲數據的持久化提供了兩個技術,分別是「 AOF 日誌和 RDB 快照」。

這兩種技術都會用各用一個日誌文件來記錄信息,但是記錄的內容是不同的。

  • AOF 文件的內容是操作命令;
  • RDB 文件的內容是二進制數據。

關於 AOF 持久化的原理我在上一篇已經介紹了,今天主要講下 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 期間,主線程處理的操作命令,可以使得數據更少的丟失


推薦閱讀

圖解 Reids | AOF 持久化

圖解 Reids | 緩存雪崩、擊穿、傳統


回過頭髮現,這次的文章圖畫的好少啊,有失圖解工具人名稱哈哈。

比較趕,就沒去細想技術圖,不過文字描述的還是很順暢,通俗易懂的,嘻嘻。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章