歡迎光臨
每天分享高質量文章

一文讓你明白Redis持久化

來自:不只Java(微信號:czd-xi)

 

閱讀文本大概需要 13 分鐘。

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

 

文章篇幅有 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 選項來決定的,一共有三個選擇:

 

1. appendfsync always:每執行一個命令儲存一次

2. appendfsync everysec(預設,推薦):每一秒鐘儲存一次

3. appendfsync no:不儲存

 

下麵說下三個的區別:

 

  • appendfsync always:每次執行完一個命令之後, WRITE 和 SAVE 都會被執行

  • appendfsync everysec:SAVE 原則上每隔一秒鐘就會執行一次。

  • appendfsync no:每次執行完一個命令之後, WRITE 會執行,SAVE 都會被忽略,只會在以下任意一種情況中被執行:

    • Redis 被關閉

    • AOF 功能被關閉

    • 系統的寫快取被掃清(可能是快取已經被寫滿,或者定期儲存操作被執行。完成依賴 OS 的寫入,一般為 30 秒左右一次)

 

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

 

 

既然 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/zhoujinyi/archive/2013/05/26/3098508.html  

https://redisbook.readthedocs.io/en/latest/internal/aof.html

    赞(0)

    分享創造快樂