宕機了,Redis數據丟了怎麼辦?
持續原創輸出,點擊上方藍字關注我

目錄
-
前言 -
什麼是AOF? -
三種寫回策略 -
日誌文件太大怎麼辦? -
AOF重寫會阻塞主線程嗎? -
AOF的缺點 -
總結
-
-
什麼是RDB? -
給哪些數據做快照? -
快照時能夠修改數據嗎? -
多久做一次快照? -
增量快照 -
AOF和RDB混合使用 -
總結
-
-
總結
前言
Redis
作為內存型的數據庫,雖然很快,依然有着很大的隱患,一旦「服務器宕機」重啟,內存中數據還會存在嗎?
很容易想到的一個方案是從後台數據恢復這些數據,如果數據量很小,這倒是一個可行的方案。但是如果數據量過大,頻繁的從後台數據庫訪問數據,壓力很大;另外一方面恢複數據的時間極慢。
對於Redis
來說,實現數據的持久化和快速恢復是至關重要。
今天這篇文章就來介紹一下Redis
持久化的兩種機制AOF
日誌、RDB
快照。
什麼是 AOF 日誌?
AOF
(Append Only File
)日誌稱之為「寫後日誌」,即是命令先執行完成,把數據寫入內存,然後才會記錄日誌。
AOF
日誌(文本形式)會將收到每一條的命令且執行成功的命令以一定的格式寫入到文本中(追加的方式)。
「寫後日誌有什麼好處呢?」 如下:
-
對於寫前日誌無論命令是否執行成功都會被記錄,但是 Redis
的寫後日誌則只有命令執行成功才會被寫入日誌,避免了日誌中存在錯誤命令; -
同時由於是命令執行成功之後才會寫入日誌,因此不會阻塞當前命令的執行。
但是AOF
日誌也有「潛在的風險」,分析如下:
-
由於是寫後日誌,如果在命令執行成功之後,在日誌未寫入磁盤之前服務器突然宕機,那重啟恢複數據的時候,這部分的數據肯定在日誌文件中不存在了,那麼將會丟失。(無法通過後台數據庫恢復的情況下) -
雖然不會阻塞當前命令的執行,由於記錄日誌也是在主線程中( Redis
是單線程),如果日誌寫入磁盤的時候突然阻塞了,肯定會影響下一個命令的執行。
為了解決上面的風險,AOF
日誌提供了三種回寫策略。
三種寫回策略
AOF
機制提供了三種回寫策略,這些都在appendfsync
配置,如下:
-
Always
(同步寫回):命令執行完成,立馬同步的將日誌寫入磁盤 -
Everysec
(每秒寫回):命令執行完成後,先將日誌寫入 AOF 文件的內存緩衝區,每隔一秒把緩衝區中內容寫入磁盤。 -
No
(操作系統控制的寫回):每個寫命令執行完,只是先把日誌寫到AOF
文件的內存緩衝區,由操作系統決定何時將緩衝區內容寫回磁盤。
其實這三中寫回策略都無法解決主線程的阻塞和數據丟失的問題,分析如下:
-
同步寫回
:基本不丟失數據,但是每步操作都會有一個慢速的落盤操作,不可避免的影響主線程性能。 -
每秒寫回
:採用一秒寫一次到 AOF 日誌文件中,但是一旦宕機還是會丟失一秒的數據。 -
操作系統控制的寫回
:在寫完緩衝區之後則會寫入磁盤,但是數據始終在緩衝區的時間內一旦宕機,數據還是會丟失。
以上三種策略優缺點總結如下表:
策略 | 優點 | 缺點 |
---|---|---|
Always | 可靠性高,數據基本不丟失 | 每個寫命令都要落盤,性能影響較大 |
Everysec | 性能適中 | 宕機時丟失一秒數據 |
No | 性能好 | 宕機時丟失數據較多 |
日誌文件太大怎麼辦?
隨着數據量的增大,AOF日誌文件難免會很大,這樣將會導致寫入和恢複數據都將變得非常慢。此時AOF提供了一種「重寫機制」解決這一問題。
❝
重寫機制理解起來很簡單,即是
Redis
會創建一個新的AOF
日誌文件,將每個鍵值對最終的值用一條命令寫入日誌文件中。❞
比如讀取了鍵值對key1:value1
,重寫機制會在新的AOF日誌文件中記錄如下一條命令:
set key1 value1
其實即是記錄多次修改的最終的值記錄在新的AOF日誌文件中,這樣當恢複數據時可直接執行該命令。
「為什麼重寫機制能夠縮小文件呢?」 當一個鍵值被多次修改後,AOF
日誌文件中將會記錄多次修改鍵值的命令,重寫機制是根據這個鍵值最新狀態為它生成「寫入」命令,這樣舊文件中的「多條」命令在重寫後的新日誌中變成了「一條」命令。
作者畫了一張重寫流程圖,僅供參考,如下:

AOF重寫會阻塞主線程嗎?
AOF重寫雖然能夠縮減日誌文件的大小,達到減少日誌記錄和數據恢復的時間,但是在數據量非常的大情況下把整個數據庫重寫後的日誌寫入磁盤是一個非常耗時的過程,難道不會阻塞主線程嗎?
「答案是:不會阻塞主線程」; 因為AOF重寫過程是由後檯子進程bgrewriteaof
來完成的,這也是為了避免阻塞主線程,導致數據庫性能下降。
其實重寫的過程分為兩個階段:「一個拷貝,兩處日誌」。
「一個拷貝」:指每次執行重寫時,主線程都fork
一個子線程bgrewriteaof
,主線程會把內存數據拷貝一份到子線程,此時子線程中包含了數據庫的最新數據。然後子線程就能在不影響主線程的情況下進行AOF重寫了。
「兩處日誌」是什麼?如下:
-
第一處日誌
:子線程重寫並未阻塞主線程,此時主線程仍然會處理請求,此時的AOF日誌仍然正在記錄著,這樣即使宕機了,數據也是齊全的。第一處日誌即是值主線程正在使用的日誌。 -
第二處日誌
:指新的AOF重寫日誌;重寫過程中的操作也會被寫到重寫日誌緩衝區,這樣重寫日誌也不會丟失最新的操作。等到拷貝數據的所有操作記錄重寫完成後,重寫日誌記錄的這些最新操作也會寫入新的 AOF 文件,以保證數據庫最新狀態的記錄。此時,我們就可以用新的 AOF 文件替代舊文件了。
❝
「總結」:
Redis
在進行AOF
重寫時,會fork
一個子線程(不會阻塞主線程)並進行內存拷貝用於重寫,然後使用兩個日誌保證重寫過程中,新寫入的數據不會丟失。❞
AOF的缺點
雖說進行了日誌重寫後,AOF日誌文件會縮減很多,但是在數據恢復過程中仍然是一條命令一條命令(由於單線程,只能順序執行)的執行恢複數據,這個恢復的過程非常緩慢。
總結
AOF這種通過逐一記錄操作命令的日誌方式,提供了三種寫回策略保證數據的可靠性,分別是Always
、Everysec
和No
,這三種策略在可靠性上是從高到低,而在性能上則是從低到高。
為了避免日誌文件過大,Redis提供了重寫的機制,每次重寫都fork一個子線程,拷貝內存數據進行重寫,將多條命令縮減成一條生成鍵值對的命令,最終重寫的日誌作為新的日誌。
什麼是RDB?
RDB
(Redis DataBase)是另外一種持久化方式:內存快照。
❝
RDB
記錄的是「某一個時刻」的內存數據,並不是操作命令。❞
這種方式類似於拍照,只保留某一時刻的形象。內存快照是將某一時刻的狀態以文件的形式寫入磁盤。這樣即使宕機了,數據也不會丟失,這個快照文件就稱為RDB
文件。
❝
由於記錄的是某個時刻的內存數據,數據恢復非常快的,不需要像AOF日誌逐一執行記錄的命令。
❞
給哪些數據做快照?
為了保證數據的可靠性,Redis執行的「全量快照」,也就是把內存中的所有數據都寫到磁盤中。
隨着數據量的增大,一次性把全部數據都寫到磁盤中勢必會造成線程阻塞,這就關係到Redis的性能了。
針對線程阻塞的問題Redis提供了兩個命令,如下:
-
save
:在主線程中執行,會導致主線程阻塞。 -
bgsave
:fork
一個子進程,專門用於寫入RDB
文件,避免了主線程的阻塞,這是Redis的默認配置。
這樣就可以使用bgsave
命令執行全量快照,既可以保證數據的可靠性也避免了主線程的阻塞。
快照時能夠修改數據嗎?
子線程執行全量快照的同時,主線程仍然在接受着請求,讀數據肯定沒有問題,但是如果個修改了數據,如何能夠保證快照的完整性呢?
「舉個栗子」:我在T
時刻進行全量快照,假設數據量有8G
,寫入磁盤的過程至少需要20S
,在這20S
的時間內,一旦內存中的數據發生了修改,則快照的完整性就破壞了。
但是如果在快照時不能修改數據,則對Redis的性能有巨大的影響,對於這個問題,Redis是如何解決的呢?
❝
Redis
藉助操作系統提供的寫時複製技術
(Copy-On-Write, COW),在執行快照的同時,正常處理寫操作。❞
其實很簡單,bgsave
命令會fork
一個子線程,這個子線程共享所有內存的數據,子線程會讀取主線程內存中的數據,將他們寫入RDB
文件。

如上圖,對於鍵值對A
的讀取並不會影響子線程,但是如果主線程一旦修改內存中一塊數據(例如鍵值對D
),這塊數據將會被複制一個副本,然後bgsave
子線程會將其寫入RDB
文件。
多久做一次快照?
快照只是記錄某一時刻的數據,一旦時間隔離很久,則服務器一旦宕機,則會丟失那段時間的數據。
比如在T1
時間做了一次快照,在T1+t
時又做了一次快照,如果在t
這個時間段內服務器突然宕機了,則快照中只保存了T1
時刻的快照,在t
時間段內的數據修改未被記錄(丟失)。如下圖:

從上圖明顯可以看出,「RDB
並不是一個完美的日誌記錄方案」,只有讓t
時間逐漸縮小,才能保證丟失的數據縮小。
「那麼問題來了,時間能夠縮短1秒
嗎?」 即是每秒執行一次快照。
❝
全量快照是記錄某一個時刻的「全部」內存數據,每秒執行一次的對Redis性能影響巨大,於是「增量快照」就出來了。
❞
增量快照
「增量快照是指做了一次全量快照之後,後續的快照只對修改的數據進行快照記錄」,這樣可以避免每次都全量快照的開銷。
增量快照的前提是Redis能夠記住修改的數據,這個功能其實開銷也是巨大的,需要保存完整的鍵值對,這對內存的消耗是巨大的。
❝
為了解決這個問題,Redis使用了
AOF
和RDB
混合使用的方式。❞
AOF和RDB混合使用
這個概念是在Redis4.0
提出的,簡單的說就是「內存快照以一定的頻率執行,比如1小時一次,在兩次快照之間,使用AOF日誌記錄這期間的所有命令操作。」
❝
混合使用的方式使得內存快照不必頻繁的執行,並且AOF記錄的也不是全部的操作命令,而是兩次快照之間的操作命令,不會出現AOF日誌文件過大的情況了,避免了AOF重寫的開銷了。
❞
這個方案既能夠用到的RDB的快速恢復的好處,又能享受都只記錄操作命令的簡單優勢,強烈建議使用。
總結
RDB
內存快照記錄的是某一個時刻的內存數據,因此能夠快速恢復;AOF
和RDB
混合使用能夠使得宕機後數據快速恢復,又能夠避免AOF
日誌文件過大。
總結
本文介紹了兩種數據恢復和持久化的方案,分別是AOF
和RDB
。
AOF
介紹了什麼?如下:
-
AOF
是寫後日誌,通過記錄操作命令持久化數據。 -
由於 AOF
是在命令執行之後記錄日誌,如果在寫入磁盤之前服務器宕機,則會丟失數據;如果寫入磁盤的時候突然阻塞,則會阻塞主線程;為了解決以上問題,AOF機制提供了三種寫回的策略,每種策略都有不同的優缺點。 -
AOF
日誌文件過大怎麼辦?AOF
通過fork
一個子線程重寫一個新的日誌文件(共享主線程的內存,記錄最新數據的寫入命令),同時子線程重寫,避免阻塞主線程。
RDB
介紹了什麼?如下:
-
RDB
是內存快照,記錄某一個時刻的內存數據,而不是操作命令。 -
Redis
提供了兩個命令,分別是save
、bgsave
來執行全量快照,這兩個命令的區別則是save
是在主線程執行,勢必會阻塞主線程,bgsave
是在fork
一個子線程,共享內存。 -
RDB通過操作系統的「寫時複製技術」,能夠保證在執行快照的同時主線程能夠修改快照。 -
由於兩次快照之間是存在間隔的,一旦服務器宕機,則會丟失兩次間隔時刻的數據, Redis4.0
開始使用AOF
日誌記錄兩次快照之間執行的命令(AOF
和RDB
混合使用)。
