一文讓你明白Redis持久化
網上雖然已經有很多類似的介紹了,但我還是自己總結歸納了一下,自認為內容和細節都是比較齊全的。
文章篇幅有 4k 多字,貨有點幹,斷斷續續寫了好幾天,希望對大家有幫助。不出意外地話,今後會陸續更新 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 選項來決定的,一共有三個選擇:
- appendfsync always:每執行一個命令儲存一次
- appendfsync everysec(預設,推薦):每一秒鐘儲存一次
- 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 重寫期間,主程序需要執行以下三個步驟:
- 執行客戶端的請求命令
- 將執行後的寫命令追加到 AOF 緩衝區
- 將執行後的寫命令追加到 AOF 重寫緩衝區
當子程序結束重寫後,會向主程序傳送一個訊號,主程序接收到之後會呼叫訊號處理函式執行以下步驟:
- 將 AOF 重寫緩衝區內容寫入新的 AOF 檔案中。此時新檔案所儲存的資料庫狀態就和當前資料庫狀態一致了
- 對新檔案進行改名, 原子地 覆蓋現有 AOF 檔案,完成新舊檔案的替換。
當函式執行完成後,主程序就繼續處理客戶端命令。
因此,在整個 AOF 重寫過程中,只有在執行訊號處理函式時才會阻塞主程序,其他時候都不會阻塞。
3. 選擇持久化方案的官方建議
到目前為止,Redis 的兩種持久化方式就介紹得差不多了。可能你會有疑惑,在實際專案中,我到底要選擇哪種持久化方案呢?下面,我貼下官方建議:
通常,如果你要想提供很高的資料保障性,那麼建議你同時使用兩種持久化方式。如果你可以接受災難帶來的幾分鐘的資料丟失,那麼你可以僅使用 RDB。
很多使用者僅使用了 AOF,但是我們建議,既然 RDB 可以時不時的給資料做個完整的快照,並且提供更快的重啟,所以最好還是也使用 RDB。
在資料恢復方面:
RDB 的啟動時間會更短,原因有兩個:
- RDB 檔案中每一條資料只有一條記錄,不會像 AOF 日誌那樣可能有一條資料的多次操作記錄。所以每條資料只需要寫一次就行了。
- 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 檔案期間產生的新的寫命令。
- 最後是官方對於兩種持久化方式選擇的一些建議
PS:本文原創釋出於微信公眾號「不只Java」,堅持原創!後臺回覆以下關鍵字獲取經典必讀書籍:
Java、MySQL、Redis、Linux、mq、資料結構、設計模式、程式設計思想、架構