宕機了,Redis 如何避免數據丟失?
作者:MaccoyCookies
原文:https://juejin.cn/post/7193597571305046071
前言
如果有人問你:"你會把 Redis 用在什麼業務場景下?"
我想你大概率會說:"我會把它當作緩存使用,因爲它把後端數據庫中的數據存儲在內存中,然後直接從內存中讀取數據,響應速度會非常快。"
沒錯,這確實是 Redis 的一個普遍使用場景,但是,這裏也有一個絕對不能忽略的問題:「一旦服務器宕機,內存中的數據將全部丟失」 。
目前,Redis 的持久化主要有兩大機制,即 「AOF(Append Only File
)日誌和 RDB(Redis DataBase
) 快照」 。
AOF
日誌是如何實現的
說到日誌,我們比較熟悉的是數據庫的寫前日誌(Write Ahead Log, WAL
),在實際寫數據前,先把修改的數據記到日誌文件中,以便故障時進行恢復。不過,AOF 日誌正好相反,它是寫後日志,"寫後" 的意思是 Redis 是先執行命令,把數據寫入內存,然後才記錄日誌。
AOF 裏記錄的是 Redis 收到的每一條命令,這些命令是以文本形式保存的。
我們以 Redis 收到 “set testkey testvalue” 命令後記錄的日誌爲例,看看 AOF 日誌的內容。其中,“*3”表示當前命令有三個部分,每部分都是由 “數字開頭,後面緊跟着具體的命令、鍵或值。這裏,數字表示這部分中的命令、鍵或值一共有多少字節。例如,3 set” 表示這部分有 3 個字節,也就是 “set” 命令。
寫後日志的優勢與風險
「爲了避免額外的檢查開銷,Redis 在向 AOF 裏面記錄日誌的時候,並不會先去對這些命令進行語法檢查」 。
如果先記日誌再執行命令的話,日誌中就有可能記錄了錯誤的命令,Redis 在使用日誌恢復數據時,就可能會出錯。而寫後日志這種方式,就是先讓系統執行命令,只有命令能執行成功,纔會被記錄到日誌中,否則,系統就會直接向客戶端報錯。
所以,Redis 使用寫後日志這一方式的一大好處是,可以避免出現記錄錯誤命令的情況。
除此之外,寫後日志一個好處:它是在命令執行後才記錄日誌,「不會阻塞當前的寫操作」 。
AOF 也有兩個潛在的風險:
-
風險一:如果剛執行完一個命令,還沒有來得及記日誌就宕機了,那麼這個命令和相應的數據就有丟失的風險。
-
如果此時 Redis 是用作緩存,還可以從後端數據庫重新讀入數據進行恢復。
-
如果 Redis 是直接用作數據庫的話,此時,因爲命令沒有記入日誌,所以就無法用日誌進行恢復了。
-
風險二:AOF 雖然避免了對當前命令的阻塞,但可能會給下一個操作帶來阻塞風險。
-
AOF 日誌也是在主線程中執行 (寫回策略爲 always 時),如果在把日誌文件寫入磁盤時,磁盤寫壓力大,就會導致寫盤很慢,進而導致後續的操作也無法執行了。
這兩個風險都是和 AOF 寫回磁盤的時機相關的。這也就意味着,如果我們能夠控制一個寫命令執行完後 AOF 日誌寫回磁盤的時機,這兩個風險就解除了。
日誌的寫回策略
AOF 機制一共有三種寫回策略,也就是 AOF 配置項 appendfsync
的三個可選值。
-
「Always 同步寫回」 :每個寫命令執行完,立馬同步地將日誌寫回磁盤;
-
「Everysec 每秒寫回」 :每個寫命令執行完,只是先把日誌寫到 AOF 文件的內存緩衝區,每隔一秒把緩衝區中的內容寫入磁盤;
-
「No 操作系統控制的寫回」 :每個寫命令執行完,只是先把日誌寫到 AOF 文件的內存緩衝區,由操作系統決定何時將緩衝區內容寫回磁盤。
針對避免主線程阻塞和減少數據丟失問題,這三種寫回策略都無法做到兩全其美。
我們就可以根據系統對高性能和高可靠性的要求,來選擇使用哪種寫回策略了。
-
想要獲得高性能,就選擇 No 策略;
-
想要得到高可靠性保證,就選擇 Always 策略;
-
允許數據有一點丟失,又希望性能別受太大影響的話,那麼就選擇 Everysec 策略。
日誌的重寫
重寫的作用
AOF 是以文件的形式在記錄接收到的所有寫命令。「隨着接收的寫命令越來越多,AOF 文件會越來越大」 。這也就意味着,我們一定要小心 AOF 文件過大帶來的性能問題,主要在於以下三個方面:
-
一是,文件系統本身對文件大小有限制,無法保存過大的文件;
-
二是,如果文件太大,之後再往裏面追加命令記錄的話,效率也會變低;
-
三是,如果發生宕機,AOF 中記錄的命令要一個個被重新執行,用於故障恢復,如果日誌文件太大,整個恢復過程就會非常緩慢,這就會影響到 Redis 的正常使用。微信搜索公衆號:架構師指南,回覆:架構師 領取資料 。
AOF 重寫機制就是在重寫時,Redis 根據數據庫的現狀創建一個新的 AOF 文件,也就是說,「讀取數據庫中的所有鍵值對,然後對每一個鍵值對用一條命令記錄它的寫入」 。重寫機制具有 “多變一” 功能。所謂的“多變一”,也就是說,舊日誌文件中的多條命令,在重寫後的新日誌中變成了一條命令。
重寫的過程
AOF 日誌由主線程寫回不同,重寫過程是由**「後臺子進程 bgrewriteaof 來完成的,這也是爲了避免阻塞主線程」** ,導致數據庫性能下降。
我把重寫的過程總結爲 “「一個拷貝,兩處日誌」 ”。
“一個拷貝” 就是指,每次執行重寫時,主線程 fork 出後臺的 bgrewriteaof 子進程。此時,fork 會把主線程的內存拷貝一份給 bgrewriteaof 子進程,這裏面就包含了數據庫的最新數據。然後,bgrewriteaof 子進程就可以在不影響主線程的情況下,逐一把拷貝的數據寫成操作,記入重寫日誌。
第一處日誌,指的是因爲主線程未阻塞,仍然可以處理新來的操作,Redis 會把這個操作寫到它的緩衝區。這樣一來,即使宕機了,這個 AOF 日誌的操作仍然是齊全的,可以用於恢復。
第二處日誌,就是指新的 AOF 重寫日誌。這個操作也會被寫到重寫日誌的緩衝區。這樣,重寫日誌也不會丟失最新的操作。等到拷貝數據的所有操作記錄重寫完成後,重寫日誌記錄的這些最新操作也會寫入新的 AOF 文件,以保證數據庫最新狀態的記錄。
此時,我們就可以用新的 AOF 文件替代舊文件了。
總結來說,每次 AOF 重寫時,Redis 會先執行一個內存拷貝,用於重寫;然後,使用兩個日誌保證在重寫過程中,新寫入的數據不會丟失。而且,「因爲 Redis 採用子進程進行日誌重寫,所以,這個過程並不會阻塞主線程」 。
正因爲記錄的是操作命令,而不是實際的數據,所以,用 AOF 方法進行故障恢復的時候,需要逐一把操作日誌都執行一遍。如果操作日誌非常多,Redis 就會恢復得很緩慢,影響到正常使用。這當然不是理想的結果。那麼,還有沒有既可以保證可靠性,還能在宕機時實現快速恢復的其他方法呢?
RDB
對 Redis 來說,它實現類似照片記錄效果的方式,把某一時刻的狀態以文件的形式寫到磁盤上,也就是快照(RDB 文件)。這樣一來,即使宕機,快照文件也不會丟失,數據的可靠性也就得到了保證。
和 AOF 相比,RDB 記錄的是某一時刻的數據,並不是操作,所以,在做數據恢復時,我們可以直接把 RDB 文件讀入內存,很快地完成恢復。
快照的原理
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 的配置選項,因爲它在可靠性和性能之間取了一個平衡。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/lGklTRK4rs6dSNwKPfsvAA