Redis怎麼保證重啟後數據不丟失

2024年2月6日 25点热度 0人点赞

Redis中的數據是存放在內存中的,這使得Redis擁有非常高效的讀寫性能。然而,內存中的數據會隨著系統重啟而丟失,那麼 Redis 是如何保證數據不丟失的呢?

持久化

Redis 的持久化主要有兩大機制,即 AOFAppend Only File)日志和 RDB(Redis DataBase) 快照」 。還有一種是混合持久化方式,並不是全新的持久化方式,而是對已有的方式的優化,Redis 4.0 之後新增的方式。混合持久化是結合了 RDB 和 AOF 的優點,在寫入的時候,先把當前的數據以 RDB 的形式寫入文件的開頭,再將後續的操作命令以 AOF 的格式存入文件,這樣既能保證 Redis 重啟時的速度,又能減低數據丟失的風險。

AOF

AOF(Append-Only File),它將所有的寫操作追加到一個日志文件中,以記錄數據庫的狀態。也就是說,在實際寫數據前,先把修改的數據記到日志文件中,以便故障時進行恢復。下面是AOF日志的實現原理:

在AOF日志中,Redis收到的"set testkey testvalue"命令會被記錄為如下格式的文本:

讓我來解釋一下這個文本的含義:

  • *3 表示當前命令有三個部分,即有三行命令。在這個例子中,這三行分別是"SET"、"testkey"和"testvalue"。
  • $3 表示後面緊跟著的部分有3個字節,也就是一個命令或者鍵的長度。
  • SET 是命令部分,表示這個操作是一個SET命令。
  • $7 表示後面緊跟著的部分有7個字節,也就是鍵的長度。
  • testkey 是鍵部分,表示要設置的鍵名為"testkey"。
  • $9 表示後面緊跟著的部分有9個字節,也就是值的長度。
  • testvalue 是值部分,表示要設置的值為"testvalue"。

寫後日志的優勢在於能夠避免記錄錯誤命令,並且不會阻塞當前的寫操作。這種方式確保了隻有成功執行的命令才會被記錄到日志中,同時也不會影響當前的寫操作。

然而,AOF 模式也存在一些風險。首先,如果在執行完一個命令後還未來得及記錄到日志就宕機了,那麼這個命令和相應的數據就有丟失的風險。其次,AOF 日志的寫回操作可能會導致磁盤寫壓力大,從而影響後續操作的執行。

針對這些風險,可以通過控制寫命令執行完後 AOF 日志寫回磁盤的時機來解決。這樣可以降低丟失數據的風險,並減少對後續操作的影響。

日志的寫回策略

這三種寫回策略都存在一定的權衡和局限性。它們在避免主線程阻塞和減少數據丟失之間做出了不同的權衡。

  • Always 同步寫回策略可以最大程度地保證數據的安全性,每個寫命令執行完後都會立即將日志同步寫回磁盤。這樣可以避免數據丟失的風險,但會對性能產生較大影響,因為每個寫操作都需要等待磁盤寫入完成才能繼續執行後續操作,可能導致主線程阻塞。
  • Everysec 每秒寫回策略則通過將日志先寫入內存緩沖區,每秒批量寫回磁盤,來平衡數據安全性和性能。這種方式可以降低主線程的阻塞時間,提高性能,但在發生故障時可能會丟失最近一秒的數據。
  • No 操作系統控制的寫回策略則完全依賴操作系統來決定何時將日志從內存緩沖區寫回磁盤。這種方式可以最大程度地提高性能,減少主線程的阻塞時間,但也增加了數據丟失的風險,因為在發生故障時可能會丟失部分未寫入磁盤的日志數據。

  • 如果對系統的性能要求比較高,可以采用 No 策略。
  • 如果對系統的可靠性要求比較高,可以采用 Always 策略。
  • 如果既希望保證數據的可靠性,又希望性能不受太大影響,可以采用 Everysec 策略。

然而,僅僅根據系統性能需求選擇寫回策略並不能完全保障系統的順利運行。這是因為AOF以文件形式記錄接收到的所有寫命令。隨著寫命令的不斷增加,AOF文件會變得越來越大。這會引發性能問題。

日志文件太大了怎麼辦?

AOF重寫機制是解決AOF文件過大問題的一種有效措施。它通過重新構建AOF文件,隻保留可以恢復當前數據庫狀態的最小命令集合,從而實現AOF文件的精簡和壓縮。

AOF重寫機制的基本原理是,Redis會在後臺創建一個新的AOF文件,然後通過遍歷數據庫的方式將當前數據庫狀態以命令的形式寫入新的AOF文件。在這個過程中,Redis會跳過那些在重寫期間已經被修改的鍵,而隻記錄鍵的最新值。這樣可以大大減小新AOF文件的大小。

AOF重寫機制的好處有:

  1. 減小AOF文件的大小,節省存儲空間。
  2. 提高AOF文件的性能,減少磁盤IO操作,加快恢復速度。
  3. 降低系統負載,減少AOF文件寫入的時間消耗。

需要註意的是,AOF重寫機制是一個後臺任務,在Redis運行時會自動觸發。您可以通過配置參數來調整重寫的觸發條件和頻率,以滿足不同的需求。

重寫的過程

和 AOF 日志由主線程寫回不同,重寫過程是由後臺線程 bgrewriteaof 來完成的,這也是為了避免阻塞主線程,導致數據庫性能下降。

我把重寫的過程總結為“一個拷貝,兩處日志”。

在AOF日志重寫過程中,主線程通過fork創建後臺的bgrewriteaof子進程,將數據庫的最新數據拷貝一份給子進程。子進程可以在不影響主線程的情況下,逐一將拷貝的數據寫成操作,並記錄到重寫日志中。

第一處日志指的是主線程仍然可以處理新操作,Redis會將這些操作寫入其緩沖區,即使發生宕機,AOF日志的操作仍然是完整的,可以用於恢復。

第二處日志是指新的AOF重寫日志,這些操作也會被寫入重寫日志的緩沖區,確保不會丟失最新的操作。當所有拷貝數據的操作記錄重寫完成後,重寫日志中記錄的這些最新操作也會被寫入新的AOF文件,以保證數據庫的最新狀態得到記錄。

此時,我們就可以用新的 AOF 文件替代舊文件了。

總結來說,每次 AOF 重寫時,Redis 會先執行一個內存拷貝,用於重寫;然後,使用兩個日志保證在重寫過程中,新寫入的數據不會丟失。而且,因為 Redis 采用額外的線程進行數據重寫,所以,這個過程並不會阻塞主線程。

RDB

RDB文件實際上是一個持久化的快照,記錄了某一時刻的數據庫狀態,包括其中的所有鍵值對和其對應的數據。

與AOF日志不同,RDB文件並不記錄操作的歷史,而是直接記錄了數據庫在某個時間點的完整狀態。這種方式的好處是,在進行數據恢復時,可以直接將RDB文件讀入內存,迅速地完成數據庫的恢復。由於RDB文件隻是一個二進制數據文件,因此在加載和解析上會比AOF日志更加高效。

快照的原理

Redis 提供了兩個命令來生成 RDB 文件,分別是 save 和 bgsave。

  • 「save」 :在主線程中執行,會導致阻塞;
  • 「bgsave」 :創建一個子進程,專門用於寫入 RDB 文件,避免了主線程的阻塞,這也是 Redis RDB 文件生成的默認配置。

我們可以通過 bgsave 命令來執行全量快照,這既提供了數據的可靠性保證,也避免了對 Redis 的性能影響。

在執行快照的同時,Redis 就會借助操作系統提供的寫時復制技術(Copy-On-Write, COW),正常處理寫操作。bgsave 子進程是由主線程 fork 生成的,可以共享主線程的所有內存數據。bgsave 子進程運行後,開始讀取主線程的內存數據,並把它們寫入 RDB 文件。

如果主線程對這些數據也都是讀操作(例如圖中的鍵值對 A),那麼,主線程和 bgsave 子進程相互不影響。但是,如果主線程要修改一塊數據(例如圖中的鍵值對 C),那麼,這塊數據就會被復制一份,生成該數據的副本(鍵值對 C’)。然後,主線程在這個數據副本上進行修改。同時,bgsave 子進程可以繼續把原來的數據(鍵值對 C)寫入 RDB 文件。

混合 AOF/RDB

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

一方面,頻繁將全量數據寫入磁盤,會給磁盤帶來很大壓力,多個快照競爭有限的磁盤帶寬,前一個快照還沒有做完,後一個又開始做了,容易造成惡性循環(所以,在 Redis 中如果有一個 bgsave 在運行,就不會再啟動第二個 bgsave 子進程)。

另一方面,bgsave 子進程需要通過 fork 操作從主線程創建出來。雖然,子進程在創建後不會再阻塞主線程,但是,「fork 這個創建過程本身會阻塞主線程」 ,而且主線程的內存越大,阻塞時間越長。

Redis 4.0 中提出了一個混合使用 AOF 日志和內存快照的方法。簡單來說,「內存快照以一定的頻率執行,在兩次快照之間,使用 AOF 日志記錄這期間的所有命令操作」 。這樣一來,快照不用很頻繁地執行,這就避免了頻繁 fork 對主線程的影響。而且,AOF 日志也隻用記錄兩次快照間的操作,也就是說,不需要記錄所有操作了,因此,就不會出現文件過大的情況了,也可以避免重寫開銷。

總結

最後,關於 AOF 和 RDB 的選擇問題,我想再給你提三點建議:

  • 數據不能丟失時,內存快照和 AOF 的混合使用是一個很好的選擇;
  • 如果允許分鐘級別的數據丟失,可以隻使用 RDB;
  • 如果隻用 AOF,優先使用 everysec 的配置選項,因為它在可靠性和性能之間取了一個平衡。