03 | AOF 日志:宕机了, Redis 如何避免数据丢失?
文章目錄
- 1. AOF日志是如何實現的
- 2. 三種寫回策略
- 3. 日志文件太大了怎么辦
- 4. AOF重寫會阻塞嗎?
1. AOF日志是如何實現的
AOF 日志是寫后日志,“寫后”的意思是 Redis 是先執行命令,把數據寫入內存,然后才記錄日志,如下圖所示:
那 AOF 為什么要先執行命令再記日志呢?要回答這個問題,我們要先知道 AOF 里記錄了什么內容。
傳統數據庫的日志,例如 redo log(重做日志),記錄的是修改后的數據,而 AOF 里記錄的是 Redis 收到的每一條命令,這些命令是以文本形式保存的。
我們以 Redis 收到“set testkey testvalue”命令后記錄的日志為例,看看 AOF 日志的內容。其中,“*3”表示當前命令有三個部分,每部分都是由“$+數字”開頭,后面緊跟著具體的令、
鍵或值。這里,“數字”表示這部分中的命令、鍵或值一共有多少字節。例如,“$3 set”表示這部分有 3 個字節,也就是“set”
命令。
但是,為了避免額外的檢查開銷,Redis 在向 AOF 里面記錄日志的時候,并不會先去對這些命令進行語法檢查。所以,如果先記日志再執行命令的話,日志中就有可能記錄了錯誤的命令,Redis 在使用日志恢復數據時,就可能會出錯。
而寫后日志這種方式,就是先讓系統執行命令,只有命令能執行成功,才會被記錄到日志中,否則,系統就會直接向客戶端報錯。所以,Redis 使用寫后日志這一方式的一大好處
是,可以避免出現記錄錯誤命令的情況。
除此之外,AOF 還有一個好處:它是在命令執行后才記錄日志,所以不會阻塞當前的寫操作。
不過,AOF 也有兩個潛在的風險。
首先,如果剛執行完一個命令,還沒有來得及記日志就宕機了,那么這個命令和相應的數據就有丟失的風險。
如果此時 Redis 是用作緩存,還可以從后端數據庫重新讀入數據進行恢復,但是,如果 Redis 是直接用作數據庫的話,此時,因為命令沒有記入日志,所以就無法用日志進行恢復了。
其次,AOF 雖然避免了對當前命令的阻塞,但可能會給下一個操作帶來阻塞風險。這是因為,AOF 日志也是在主線程中執行的,如果在把日志文件寫入磁盤時,磁盤寫壓力大,就會導致寫盤很慢,進而導致后續的操作也無法執行了。
仔細分析的話,你就會發現,這兩個風險都是和 AOF 寫回磁盤的時機相關的。這也就意味著,如果我們能夠控制一個寫命令執行完后 AOF 日志寫回磁盤的時機,這兩個風險就解除了。
2. 三種寫回策略
其實,對于這個問題,AOF 機制給我們提供了三個選擇,也就是 AOF 配置項appendfsync 的三個可選值。
- Always,同步寫回:每個寫命令執行完,立馬同步地將日志寫回磁盤;
- Everysec,每秒寫回:每個寫命令執行完,只是先把日志寫到 AOF 文件的內存緩沖區,每隔一秒把緩沖區中的內容寫入磁盤;
- No,操作系統控制的寫回:每個寫命令執行完,只是先把日志寫到 AOF 文件的內存緩沖區,由操作系統決定何時將緩沖區內容寫回磁盤。
針對避免主線程阻塞和減少數據丟失問題,這三種寫回策略都無法做到兩全其美。我們來分析下其中的原因。
- 同步寫回 可以做到基本不丟數據,但是它在每一個寫命令后都有一個慢速的落盤操作,不可避免地會影響主線程性能;
- 雖然“操作系統控制的寫回”在寫完緩沖區后,就可以繼續執行后續的命令,但是落盤的時機已經不在 Redis 手中了,只要 AOF 記錄沒有寫回磁盤,一旦宕機對應的數據就丟失了;
- “每秒寫回”采用一秒寫回一次的頻率,避免了“同步寫回”的性能開銷,雖然減少了對系統性能的影響,但是如果發生宕機,上一秒內未落盤的命令操作仍然會丟失。所以,這只能算是,在避免影響主線程性能和避免數據丟失兩者間取了個折中。
到這里,我們就可以根據系統對高性能和高可靠性的要求,來選擇使用哪種寫回策略了。總結一下就是:
- 想要獲得高性能,就選擇 No 策略;
- 如果想要得到高可靠性保證,就選擇Always 策略;
- 如果允許數據有一點丟失,又希望性能別受太大影響的話,那么就選擇Everysec 策略。
但是,按照系統的性能需求選定了寫回策略,并不是“高枕無憂”了。AOF 是以文件的形式在記錄接收到的所有寫命令。隨著接收的寫命令越來越多,AOF 文件會越來越大。這也就意味著,我們一定要小心 AOF 文件過大帶來的性能問題。這里的“性能問題”,主要在于以下三個方面:
- 文件系統本身對文件大小有限制,無法保存過大的文件;
- 如果文件太大,之后再往里面追加命令記錄的話,效率也會
變低; - 如果發生宕機,AOF 中記錄的命令要一個個被重新執行,用于故障恢復,如果日志文件太大,整個恢復過程就會非常緩慢,這就會影響到 Redis 的正常使用。
3. 日志文件太大了怎么辦
簡單來說,AOF 重寫機制就是在重寫時,Redis 根據數據庫的現狀創建一個新的 AOF 文件,也就是說,讀取數據庫中的所有鍵值對,然后對每一個鍵值對用一條命令記錄它的寫
入。比如說,當讀取了鍵值對“testkey”:“testvalue”之后,重寫機制會記錄 set testkey testvalue 這條命令。這樣,當需要恢復時,可以重新執行該命令,實現“testkey”:“testvalue”的寫入。
實際上,重寫機制具有“多變一”功能。所謂的“多變一”,也就是說,舊日志文件中的多條命令,在重寫后的新日志中變成了一條命令。
我們知道,AOF 文件是以追加的方式,逐一記錄接收到的寫命令的。當一個鍵值對被多條寫命令反復修改時,AOF 文件會記錄相應的多條命令。但是,在重寫的時候,是根據這個鍵值對當前的最新狀態,為它生成對應的寫入命令。這樣一來,一個鍵值對在重寫日志中只用一條命令就行了,而且,在日志恢復時,只用執行這條命令,就可以直接完成這個鍵值對的寫入了。
下面這張圖就是一個例子:
當我們對一個列表先后做了 6 次修改操作后,列表的最后狀態是[“D”,“C”,“N”],此時,只用 LPUSH u:list “N”,“C”, "D"這一條命令就能實現該數據的恢復,這就節省了五條命令的空間。對于被修改過成百上千次的鍵值對來說,重寫能節省的空間當然就更大了。不過,雖然 AOF 重寫后,日志文件會縮小,但是,要把整個數據庫的最新數據的操作日志
都寫回磁盤,仍然是一個非常耗時的過程。這時,我們就要繼續關注另一個問題了:重寫會不會阻塞主線程?
4. AOF重寫會阻塞嗎?
和 AOF 日志由主線程寫回不同,重寫過程是由后臺線程 bgrewriteaof 來完成的,這也是為了避免阻塞主線程,導致數據庫性能下降。我把重寫的過程總結為“一個拷貝,兩處日志”。
“一個拷貝”就是指,每次執行重寫時,主線程 fork 出后臺的 bgrewriteaof 子進程。此時,fork 會把主線程的內存拷貝一份給 bgrewriteaof 子進程,這里面就包含了數據庫的最新數據。然后,bgrewriteaof 子進程就可以在不影響主線程的情況下,逐一把拷貝的數據寫成操作,記入重寫日志。“兩處日志”因為主線程未阻塞,仍然可以處理新來的操作。
此時,如果有寫操作,第一處日志就是指正在使用的 AOF 日志,Redis 會把這個操作寫到它的緩沖區。這樣一來,即使宕機了,這個 AOF 日志的操作仍然是齊全的,可以用于恢復。第二處日志,就是指新的 AOF 重寫日志。這個操作也會被寫到重寫日志的緩沖區。這樣,重寫日志也不會丟失最新的操作。等到拷貝數據的所有操作記錄重寫完成后,重寫日志記錄的這些最新操作也會寫入新的 AOF 文件,以保證數據庫最新狀態的記錄。此時,我們就可以用新的 AOF 文件替代舊文件了。
總結來說,每次 AOF 重寫時,Redis 會先執行一個內存拷貝,用于重寫;然后,使用兩個日志保證在重寫過程中,新寫入的數據不會丟失。而且,因為 Redis 采用額外的線程進行數據重寫,所以,這個過程并不會阻塞主線程。
總結
以上是生活随笔為你收集整理的03 | AOF 日志:宕机了, Redis 如何避免数据丢失?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 02 | 高性能 IO 模型:为什么单线
- 下一篇: 04 | 内存快照:宕机后, Redis