Redis 持久化


Redis 持久化簡介

持久化就是把內存的數據寫到磁盤中,防止服務器宕機導致內存數據丟失。

Redis 支持兩種方式的持久化,一種是RDB的方式,一種是AOF的方式。

RDB 持久化

RDB 就是 Redis DataBase 的縮寫,中文名為快照 / 內存快照,RDB持久化是把當前進程數據生成快照保存到磁盤上的過程,由於是某一時刻的快照,那么快照的值要早於或等於內存的值。

觸發方式

觸發RDB持久化的方式有兩種,分別是手動觸發和自動觸發

手動觸發

手動觸發分別對應 save 和 bgsave 命令。

  • save命令:阻塞當前 Redis 服務器,直到 RDB 過程完成為止,對於內存比較大的實例會造成長時間的阻塞,線上環境不建議使用。
  • bgsave命令:Redis 進程執行 fork 操作創建子進程,RDB 持久化過程由 子進程 負責,完成后自動結束。阻塞只發生在 fork 階段,一般時間很短。

bgsave流程圖如下所示:

執行過程:

  • 執行bgsave命令
  • Redis 父進程判斷當前是否存在正在執行的子進程,如果存在,bgsave命令直接返回。
  • 父進程執行fork操作創建子進程fork 操作過程中父進程會阻塞
  • 父進程 fork 完成后,父進程繼續接收並處理客戶端的請求,而子進程開始將內存中的數據寫進硬盤的臨時數據
  • 當子進程寫完所有數據后會用該臨時文件替換舊的RDB文件

自動觸發

在以下四種情況時會自動觸發。

  • redis.conf 中配置 save m n ,即在 m 秒內有 n 次修改時,自動觸發 bgsave 生成 rdb 文件。
  • 主從復制時, 從節點 要從 主節點 進行 全量復制 時,也會觸發bgsave操作,生成當時的快照發送到從節點。
  • 執行 debug reload 命令重新加載 redis 時如果沒有開啟 aof 持久化,那么也會觸發 bgsave
  • 默認情況下執行 shutdown 命令時,如果沒有開啟 aof 持久化,那么也會觸發 bgsave 操作。

深入理解

  • 由於生產環境中我們為Redis開辟的內存區域都比較大(例如6GB),那么將內存中的數據同步到硬盤的過程可能就會持續比較長的時間,而實際情況是這段時間Redis服務一般都會收到數據寫操作的請求。那么如何保證數據一致性呢?

RDB 中的核心思路是 Copy-on-Write (寫時復制),來保證在進行快照操作的這段時間,需要壓縮寫入磁盤上的數據在內存中不會發生變化。在正常的快照操作中,一方面 Redis 主進程 會 fork 一個新的快照進程專門來做這個事情,這樣保證了 Redis 服務 不會停止對客戶端包括寫請求在內的任何響應。另一方面,在 fork 過程中發生的數據變化會以副本的方式存放在另一個新的內存區域,待快照操作結束后才會同步到原來的內存區域。

舉個例子:如果主線程對這些數據也都是讀操作(例如圖中的鍵值對A),那么,主線程 和 bgsave 子進程相互不影響。但是,如果主線程要修改一塊數據(例如圖中的鍵值對C),那么,這塊數據就會被復制一份,生成該數據的副本。然后,bgsave 子進程會把這個副本數據寫入到 rdb 文件,而在這個過程中,主線程仍然可以直接修改原來的數據。

  • 在進行快照操作的這段時間,如果發生服務崩潰怎么辦?

在沒有將數據全部寫入到磁盤前,這次快照操作都不算成功。如果出現了服務崩潰的情況,將以上一次完整的 RDB 快照文件 作為回復內存數據的參考。也就是說,在快照操作過程中不能影響上一次的備份數據。Redis服務會在磁盤上創建一個臨時文件進行數據操作,待操作成功后才會用這個臨時文件替換掉上一次的備份。

  • 可以每秒做一次快照嗎?

顯然是不可以的,雖然 bgsave 執行時不會阻塞主線程,但是,如果頻繁的執行全量快照,也會帶來兩方面的開銷:

  • 頻繁的將全量數據寫入磁盤,會給磁盤帶來很大的壓力,多個快照競爭有限的磁盤帶寬,前一個快照還沒有做完,后一個又開始做了,容易造成惡性循環。
  • bgsave 子進程需要通過 fork 操作從主線程創建出來。雖然,子進程在創建后不會再阻塞主線程,但是,fork 這個創建進程的操作本身 會阻塞主線程,而且主線程的內存越大,阻塞時間越長。如果頻繁的 fork 出 bgsave 子進程,這就會頻繁的阻塞主線程了。

RDB優缺點

  • 優點:

    • Redis 加載 RDB 文件恢復數據要遠遠快於 AOF 方式。
    • RDB 文件是某個時間節點的快照,默認使用LZF算法進行壓縮,壓縮后的文件體積遠遠小於內存大小,適用於備份、全量復制等場景。
  • 缺點:

    • RDB 方式的實時性不夠,無法做到秒級持久化
    • 每次調用 bgsave 都需要 fork子進程,fork子進程 屬於重量級操作,頻繁執行成本較高
    • RDB 文件是 二進制 的,沒有可讀性,AOF 文件在了解其結構的情況下可以手動修改或者補全。
    • 版本兼容RDB文件問題,Redis 版本升級過程中有多個格式的 RDB 版本,存在老版本 Redis 無法兼容 新版 RDB格式的問題

AOF 持久化

Redis 是“寫后”日志,Redis 先執行命令,把數據寫入內存,然后才記錄日志。日志里記錄的是Redis收到的每一條命令,這些命令是以文本形式保存。

PS:大多數的數據庫采用的是寫前日志(WAL),例如MySQL,通過寫前日志和兩階段提交,實現數據和邏輯的一致性。

而 AOF 日志采用寫后日志,即先寫內存,后寫日志

為什么采用寫后日志?

Redis 要求高性能,采用寫日志的有兩方面好處

  • 避免額外的檢查開銷:Redis 在向AOF里面記錄日志的時候,並不會先對這些命令進行語法檢查。所以,如果先記日志再執行命令的話,日志中就有可能記錄了錯誤的命令,Redis在使用日志恢復數據時,就可能會出錯。
  • 不會阻塞當前的寫操作。

這種方式存在一定風險:

  • 如果命令執行完成,寫日志之前宕機了,會丟失數據。
  • 主線程寫磁盤壓力大,導致寫盤慢,阻塞后續操作。

AOF的實現

AOF日志記錄 Redis 的每個寫指令,步驟分為:命令追加(append)、文件寫入(write)和 文件同步(sync)。

  • 命令追加:當 AOF 持久化功能打開時,服務器在執行完一個 寫命令 之后,會以協議格式將被執行的寫命令追加到服務器的 aof_buf 緩沖區。
  • 文件寫入和同步:關於何時將 aof_buf 緩沖區的內容寫入 AOF 文件中,Redis提供了三種寫回策略:

Always同步寫回:每個寫命令執行完,立馬同步地將日志寫回磁盤;

Everysec每秒寫回:每個寫命令執行完,只是先把日志寫到 AOF文件 的內容緩沖區,每隔 1s 把緩沖區的內容寫入磁盤;

No操作系統控制的寫回:每個寫命令執行完,只是先把日志寫到 AOF 文件的內容緩沖區,由操作系統決定何時將緩沖區內容寫回磁盤。

  • 三種寫回策略的優缺點

上面的三種策略體現了一個重要原則:trade-off,取舍,指在性能和可靠性保證之間做取舍。

關於 AOF 的同步策略是涉及到操作系統的 write 函數 和 fsync 函數的,在《Redis設計與實現》是這樣說明的。

為了提高文件寫入效率,在現代操作系統中,當用戶調用write函數,將一些數據寫入文件時,操作系統通常會將數據暫存到一個內存緩沖區里,當緩沖區的空間被填滿或超過了指定時限后,才真正將緩沖區的數據寫入到磁盤里。

這樣的操作雖然提高了效率,但也為數據寫入帶來了安全問題:如果計算機停機,內存緩沖區中的數據會丟失。為此,系統提供了fsync、fdatasync同步函數,可以強制操作系統立刻將緩沖區中的數據寫入到硬盤中,從而確保寫入數據的安全性。

深入理解AOF重寫

Redis 通過創建一個新的 AOF 文件來替換現有的 AOF ,新舊兩個 AOF 文件保存的數據相同,但新 AOF 文件中沒有冗余命令。

  • AOF 重寫會阻塞嗎?

AOF重寫過程是由后台進程bgrewriteaof來完成的。主線程 fork 出后台的 bgrewriteaof 子進程,fork 會把主線程的內存拷貝一份給bgrewriteaof 子進程,這里面就包含了數據庫的最新數據。然后,bgrewriteaof 子進程就可以在不影響主線程的情況下,逐一把拷貝的數據寫成操作,記入重寫日志。

所以 AOF 在重寫時,在 fork 進程時是會阻塞主線程的。

  • AOF 日志何時會重寫?

有兩個配置項控制AOF重寫的觸發:

  1. auto-aof-rewrite-min-size:表示運行AOF重寫時 文件的最小大小,默認為64MB。
  2. auto-aof-rewrite-percentage:這個值的計算方式是,當前 aof 文件大小和上一次重寫后 aof 文件大小的差值,再除以上一次重寫后 aof 文件大小。也就是當前 aof文件 比上一次重寫后 aof文件 的增量大小,和上一次重寫后 aof文件 大小的比值。
  • 重寫日志時,有新數據寫入怎么處理?

重寫過程總結為:“一個拷貝,兩處日志”。在 fork 出子進程時的拷貝,以及在重寫時,如果有新數據寫入,主線程就會將命令記錄到兩個 aof 日志內存緩沖區中。如果 AOF 寫回策略配置的是 always ,則直接將命令寫回舊的日志文件,並且保存一份命令至 AOF 重寫緩沖區,這些操作對新的日志文件是不存在影響的。(舊的日志文件:主線程使用的日志文件,新的日志文件: bgrewriteaof 進程使用的日志文件)。

而在 bgrewriteaof 子進程完成對日志文件的重寫操作后,會提示主線程已經完成重寫操作,主線程會將 AOF 重寫緩沖區中的命令追加到新的日志文件后面。這時候在高並發的情況下,AOF 重寫緩沖區積累可能會很大,這樣就會造成阻塞,Redis后來通過 Linux 管道技術讓 aof 重寫期間就能同時進行回放,這樣 aof 重寫結束后只需回放少量剩余數據即可。

最后通過修改文件名的方式,保證文件切換的原子性。

在 AOF 重寫日志期間發生宕機的話,因為日志文件還沒切換,所以恢復數據時,用的還是舊的日志文件。

從持久化中恢復數據

流程如下:

  • redis 重啟時判斷是否開啟 aof ,如果開啟 aof ,那么就優先加載 aof 文件;
  • 如果 aof 存在,那么就去加載 aof 文件,加載成功的話 redis 重啟成功,如果 aof 文件加載失敗,那么就會打印日志表示啟動失敗,此時可以去修復 aof 文件后重新啟動;
  • 若 aof 文件不存在,那么 redis 就會轉去加載 rdb 文件,如果 rdb 文件不存在,redis 直接啟動成功;
  • 如果 rdb 文件存在就會去加載 rdb 文件恢復數據,若加載失敗則打印日志提示啟動失敗,若加載成功,那么 redis 重啟成功,且使用 rdb 文件恢復數據;

那么為什么會優先加載 aof 文件呢?

因為 aof 保存的數據更完整,因為 aof 基本上最多損失 1s 的數據。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM