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

小林coding發表於2021-06-28

大家好,我是小林。

雖說 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 | 快取雪崩、擊穿、傳統


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

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

相關文章