1. 程式人生 > >一文讓你明白Redis持久化

一文讓你明白Redis持久化

獲取 詳細介紹 aps 記錄 系統內存 可能 子進程 副本 行修改

網上雖然已經有很多類似的介紹了,但我還是自己總結歸納了一下,自認為內容和細節都是比較齊全的。

文章篇幅有 4k 多字,貨有點幹,斷斷續續寫了好幾天,希望對大家有幫助。不出意外地話,今後會陸續更新 Redis 相關的文章,和大家一起學習Redis 持久化。

好了,下面開始回歸正文:

Redis 一共有 2 種持久化方式,分別是 RDB 和 AOF,下面我來詳細介紹兩種方式在各個過程所做的事情,特點等等。

1. RDB 持久化

RDB 持久化是 Redis 默認的持久化方式。

它所生成的 RDB 文件是一個壓縮的二進制文件,通過該文件可以還原生成 RDB 文件時的數據庫狀態

PS:數據庫狀態是指 Redis 服務器的非空數據庫以及他們鍵值對的統稱

1.1 RDB 文件的創建

有兩個命令可以生成 RDB 文件,一個是 SAVE、另一個是 BGSAVE。

兩者的區別在於:前者會阻塞 Redis 服務器進程,直到 RDB 文件創建完畢為止。

而在服務器進程阻塞期間,服務器是不能處理任何命令請求的。

後者則不會阻塞服務器進程,因為是通過 fork 一個子進程,並讓其去創建 RDB 文件,而服務器進程(父進程)繼續則繼續處理命令請求。

當寫完數據庫狀態後,新 RDB 文件就會原子地替換舊的 RDB 文件。

此處小提問:如果在執行 BGSAVE 期間,客戶端發送 SAVE、BGSAVE 或 BGREWRITEAOF 命令給服務端,服務端會如何處理呢?

答案:在執行 BGSAVE 期間,上述三個命令都不會被執行。

詳細原因:前兩個會被直接拒絕,原因是為了避免父子進程同時執行兩個 rdbSave 調用,防止產生競爭條件。
而 BGREWRITEAOF 命令則是會被延遲到 BGSAVE 命令執行之後再執行。
但如果是 BGREWRITEAOF 命令正在執行,此時客戶端發送 BGSAVE 命令則會被拒絕。
因為 BGREWRITEAOF 和 BGSAVE 都是由子進程執行的,所以在操作方面沒有沖突的地方,不能同時執行的原因是性能上的考慮——並發出兩個子進程,並且這兩個子進程都會同時執行大量 io(磁盤寫入)操作

1.2 RDB 文件的載入

RDB 文件的載入是在服務器啟動時自動執行的,所以沒有用於載入的命令,期間阻塞主進程。

只要沒有開啟 AOF 持久化功能,在啟動時檢測到有 RDB 文件,就會自動載入。

當服務器有開啟 AOF 持久化功能時,服務器將會優先使用 AOF 文件來還原數據庫狀態。原因是 AOF 文件的更新頻率通常比 RDB 文件的更新頻率高。

1.3 自動間隔性保存

對於 RDB 持久化而言,我們一般都會使用 BGSAVE 來持久化,畢竟它不會阻塞服務器進程。

在 Redis 的配置文件,有提供設置服務器每隔多久時間來執行 BGSAVE 命令。

Redis 默認是如下配置:
save 900 1 // 900 秒內,對數據庫至少修改 1 次。下面同理
save 300 10
save 60 10000

只要滿足其中一種情況,服務器就會執行 BGSAVE 命令。

2. AOF 持久化

我們從上面的介紹知道,RDB 持久化通過保存數據庫狀態來持久化。而 AOF 與之不同,它是通過保存對數據庫的寫命令來記錄數據庫狀態

比如執行了 set key 123,Redis 就會將這條寫命令保存到 AOF 文件中。

在服務器下次啟動時,就可以通過載入和執行 AOF 文件中保存的命令,來還原服務器關閉前的數據庫狀態了。

總體流程和 RDB 持久化一樣 —— 都是創建一個 xxx 文件、在服務器下次啟動時就載入這個文件來還原數據

那麽,AOF 持久化具體是怎麽實現的呢?

2.1 AOF 持久化實現

AOF 持久化功能的實現可以分為 3 個步驟:命令追加、文件寫入、文件同步

其中命令追加很好理解,就是將寫命令追加到 AOF 緩沖區的末尾。

那文件寫入和文件同步怎麽理解呢?剛開始我也一臉懵逼,終於在網上找到了答案,參考見文末,有興趣的讀者可以去看看。

先不賣關子了,簡單一句話解釋就是:前者是緩沖區內容寫到 AOF 文件,後者是將 AOF 文件保存到磁盤

ok,明白什麽意思之後,我們稍微詳細看下這兩個東西是什麽鬼。

在《Redis設計與實現》中提到,Redis 服務器進程就是一個事件循環,這個循環中的文件事件(socket 的可讀可寫事件)負責接收客戶端的命令請求,以及向客戶端發送命令結果。

因為服務器在處理文件事件時,可能會發生寫操作,使得一些內容會被追加到 AOF 緩沖區末尾。所以,在服務器每次結束一個事件循環之前 ,都會調用 flushAppendOnlyFile 方法。

這個方法執行以下兩個工作:

  • WRITE:根據條件,將緩沖區內容寫入到 AOF 文件。
  • SAVE:根據條件,調用 fsync 或 fdatasync 函數,將 AOF 文件保存到磁盤中。

兩個步驟都需要根據一定的條件來執行,而這些條件由 Redis 配置文件中的 appendfsync 選項來決定的,一共有三個選擇:

  1. appendfsync always:每執行一個命令保存一次
  2. appendfsync everysec(默認,推薦):每一秒鐘保存一次
  3. appendfsync no:不保存

下面說下三個的區別:

  • appendfsync always:每次執行完一個命令之後, WRITE 和 SAVE 都會被執行
  • appendfsync everysec:SAVE 原則上每隔一秒鐘就會執行一次。
  • appendfsync no:每次執行完一個命令之後, WRITE 會執行,SAVE 都會被忽略,只會在以下任意一種情況中被執行:

    • Redis 被關閉
    • AOF 功能被關閉
    • 系統的寫緩存被刷新(可能是緩存已經被寫滿,或者定期保存操作被執行。完成依賴 OS 的寫入,一般為 30 秒左右一次)

而對於操作特性來分析的話,則是如下情況:

模式 WRITE 是否阻塞主進程 SAVE 是否阻塞主進程 停機時丟失的數據量
appendfsync always 阻塞 阻塞 最多只丟失一個命令的數據
appendfsync everysec 阻塞 不阻塞 一般情況下不超過 2 秒鐘的數據
appendfsync no 阻塞 阻塞 操作系統最後一次對 AOF 文件觸發 SAVE 操作之後的數據

既然 AOF 持久化是通過保存寫命令到文件的,那隨著時間的推移,這個 AOF 文件記錄的內容就越來越多,文件體積也就越來越大,對其進行數據還原的時間也就越來越久。

針對這個問題,Redis 提供了 AOF 文件重寫功能。

2.2 AOF 重寫

通過該功能來創建一個新的 AOF 文件來代替舊文件。並且兩個文件所保存的數據庫狀態一樣,但新文件不會包含任何冗余命令,所以新文件要比舊文件小得多。

而為什麽新文件不會包含任何冗余命令呢?

那是因為這個重寫功能是通過讀取服務器當前的數據庫狀態來實現的。雖然叫做「重寫」,但實際上並沒有對舊文件進行任何讀取修改。

比如舊文件保存了對某個 key 有 4 個 set 命令,經過重寫之後,新文件只會記錄最後一次對該 key 的 set 命令。因此說新文件不會包含任何冗余命令

因為重寫涉及到大量 IO 操作,所以 Redis 是用子進程來實現這個功能的,否則將會阻塞主進程。該子進程擁有父進程的數據副本,可以避免在使用鎖的情況下,保證數據的安全性。

那麽這裏又會存在一個問題,子進程在重寫過程中,服務器還在繼續處理命令請求,新命令可能會對數據庫進行修改,這會導致當前數據庫狀態和重寫後的 AOF 文件,所保存的數據庫狀態不一致

為了解決這個問題,Redis 設置了一個 AOF 重寫緩沖區。在子進程執行 AOF 重寫期間,主進程需要執行以下三個步驟:

  1. 執行客戶端的請求命令
  2. 將執行後的寫命令追加到 AOF 緩沖區
  3. 將執行後的寫命令追加到 AOF 重寫緩沖區

當子進程結束重寫後,會向主進程發送一個信號,主進程接收到之後會調用信號處理函數執行以下步驟:

  1. 將 AOF 重寫緩沖區內容寫入新的 AOF 文件中。此時新文件所保存的數據庫狀態就和當前數據庫狀態一致了
  2. 對新文件進行改名,原子地覆蓋現有 AOF 文件,完成新舊文件的替換。

當函數執行完成後,主進程就繼續處理客戶端命令。

因此,在整個 AOF 重寫過程中,只有在執行信號處理函數時才會阻塞主進程,其他時候都不會阻塞

3. 選擇持久化方案的官方建議

到目前為止,Redis 的兩種持久化方式就介紹得差不多了。可能你會有疑惑,在實際項目中,我到底要選擇哪種持久化方案呢?下面,我貼下官方建議:

通常,如果你要想提供很高的數據保障性,那麽建議你同時使用兩種持久化方式。如果你可以接受災難帶來的幾分鐘的數據丟失,那麽你可以僅使用 RDB

很多用戶僅使用了 AOF,但是我們建議,既然 RDB 可以時不時的給數據做個完整的快照,並且提供更快的重啟,所以最好還是也使用 RDB。

在數據恢復方面:
RDB 的啟動時間會更短,原因有兩個:

  1. RDB 文件中每一條數據只有一條記錄,不會像 AOF 日誌那樣可能有一條數據的多次操作記錄。所以每條數據只需要寫一次就行了。
  2. RDB 文件的存儲格式和 Redis 數據在內存中的編碼格式是一致的,不需要再進行數據編碼工作,所以在 CPU 消耗上要遠小於 AOF 日誌的加載。

註意:

上面說了 RDB 快照的持久化,需要註意:在進行快照的時候(save),fork 出來進行 dump 操作的子進程會占用與父進程一樣的內存,真正的 copy-on-write,對性能的影響和內存的耗用都是比較大的。比如機器 8G 內存,Redis 已經使用了 6G 內存,這時 save 的話會再生成 6G,變成 12G,大於系統的 8G。這時候會發生交換;要是虛擬內存不夠則會崩潰,導致數據丟失。所以在用 redis 的時候一定對系統內存做好容量規劃。

目前,通常的設計思路是利用復制(Replication)機制來彌補 aof、snapshot 性能上的不足,達到了數據可持久化。即 Master 上 Snapshot 和 AOF 都不做,來保證 Master 的讀寫性能,而 Slave 上則同時開啟 Snapshot 和 AOF 來進行持久化,保證數據的安全性。

總結

文章知識點有點多和雜,我總結一下,幫助他們回顧內容:

  • RDB 持久化是 Redis 默認持久化方式,通過保存數據庫鍵值對來記錄狀態來持久化,由 SAVE 和 BGSAVE 命令來創建 RDB 文件。前者阻塞 Redis 主進程,後者不會。
  • RDB 可以在配置文件設置每隔多久時間來執行 BGSAVE 命令
  • AOF 通過追加寫命令來保存當前數據庫狀態。其持久化功能的實現可以分為 3 個步驟:命令追加(到 AOF 緩沖區)、文件寫入(緩沖區內容寫到 AOF 文件)、文件同步(AOF 文件保存磁盤)
  • 其中文件同步和保存可以通過配置文件的 appendfsync 選項來決定
  • 為了解決 AOF 文件越來越大的問題,Redis 提供了 AOF 重寫功能,並且不會阻塞主進程。
  • 為了解決 AOF 重寫過程中,新 AOF 文件所保存的數據庫狀態和當前數據庫狀態可能不一致的問題,Redis 引入了 AOF 重寫緩沖區,用於保存子進程在重寫 AOF 文件期間產生的新的寫命令。
  • 最後是官方對於兩種持久化方式選擇的一些建議
參考:
《redis設計與實現》
https://www.cnblogs.com/zhouj...
https://redisbook.readthedocs...

PS:本文原創發布於微信公眾號「不只Java」,堅持原創!後臺回復以下關鍵字獲取經典必讀書籍
Java、MySQL、Redis、Linux、mq、數據結構、設計模式、編程思想、架構

技術分享圖片

一文讓你明白Redis持久化