大家都知道Redis經常被使用在快取的場景中,那有沒有想過這麼一個問題,一旦伺服器當機,記憶體中的資料全部丟失,我們該如何進行恢復呢?如果直接從後端資料庫恢復,不僅會給資料庫帶來巨大的壓力,還會使上層應用響應變慢。所以redis的持久化機制是很重要的。接下來我們一起來探討一下Redis的持久化機制。目前Redis持久化主要有兩大機制,即AOF(Append Only File)日誌和RDB快照。接下來我們就來分別學習一下。
AOF日誌
AOF日誌,即寫後日志,它的含義是Redis先執行命令,把資料寫入記憶體,然後再寫入日誌。Redis為什麼要先執行命令後寫入日誌呢?首先我們來看一下AOF日誌裡記錄了什麼內容。AOF記錄的是Redis收到的每一條命令,這些日誌以文字的形式儲存。假如我們執行了set hello world命令,AOF的內容如下:
其中“*3”代表當前命令有3部分,每部分是由”$+數字”開頭,後面緊跟著具體命令、鍵和值。這裡的數字代表後面的命令、鍵和值一共有多少個位元組。例如 “$3 set”表示這部分有3個位元組,也就是“set”命令。
Redis為了避免額外的檢查開銷,再往AOF裡寫入日誌的時候,並不會對這些命令進行語法檢查。所以如果先寫日誌再執行命令,日誌中就可能會記錄一些錯誤的命令,Redis使用日誌恢復的時候就會出錯。而寫後日志這種方式,就是先去執行命令,如果命令執行出錯,則不寫入日誌,只有執行成功的命令才會寫入日誌中。所以Redis使用寫後日志這一方式的一大好處是,可以避免出現記錄錯誤命令的情況。AOF還有一大好處,它是在命令執行後才記錄日誌,所以不會阻塞當前的寫操作。
不過,AOF也有潛在的兩個風險,首先如果剛執行完一個命令,還沒來的及寫日誌就當機了,那麼這個命令和相應的資料就有丟失的風險。其次,AOF雖然避免了對當前命令的阻塞,但可能會給下一個操作帶來阻塞風險。這是因為,AOF日誌也是在主執行緒中執行的,如何在把日誌檔案寫入磁碟很慢時,就會阻塞後續操作。針對這個問題,AOF給我們提供了三種寫回策略,也就是AOF的配置項appendfsync的三個可選值。
- Always,同步寫回:每個寫命令執行完,立馬同步地將日誌寫回磁碟;
- Everysec,每秒寫回:每個寫命令執行完,只是先把日誌寫到AOF檔案的記憶體緩衝區,每隔一秒把緩衝區中的內容寫入磁碟;
- No,作業系統控制的寫回:每個寫命令執行完,只是先把日誌寫到AOF檔案的記憶體緩衝區,由作業系統決定何時將緩衝區內容寫回磁碟。
這三種寫回策略都無法做到兩全其美,都有自己的優缺點,我們只能根據我們的業務場景,是需要高效能還是高可靠性來選擇不同的寫回策略。
最後,AOF還有一個問題,就是AOF以檔案的形式記錄在磁碟裡。隨著Redis接受的寫命令越來越多,那麼AOF日誌檔案也會越來越大,所以需要採取一定的手段來控制AOF日誌檔案的大小。這個時候,AOF重寫機制就派上用場了。AOF重寫機制是指在重寫時,Redis會根據資料庫的現狀建立一個新的AOF檔案,也就是說,讀取資料庫中的所有鍵值對,然後對每一個鍵值對用一條命令記錄它的寫入。為什麼重寫機制可以把日誌檔案變小呢?實際上,重寫機制具有“多變一”功能。所謂的“多變一”,也就是說,舊日誌檔案中的多條命令,在重寫後的新日誌中變成了一條命令。例如我們對某個key進行了6次寫入操作,那麼舊的日誌檔案中就會有6條記錄,而重寫後的日誌檔案中就只有一條命令,所以AOF重寫會減小檔案的大小。那麼AOF重寫會阻塞主執行緒嗎?畢竟把整個Redis的資料庫的最新資料的操作日誌都寫回磁碟,仍然是一個非常耗時的過程。和AOF日誌由主執行緒寫回不同,重寫過程是由後臺子程式bgrewriteaof來完成的,這也是為了避免阻塞主執行緒,導致Redis的效能下降。每次執行重寫時,主執行緒 fork 出後臺的 bgrewriteaof 子程式。此時,fork 會把主執行緒的記憶體拷貝(採用作業系統的寫時複製技術,不會真正的拷貝,寫時複製技術下面會分析)一份給 bgrewriteaof 子程式,這裡面就包含了資料庫的最新資料。然後,bgrewriteaof 子程式就可以在不影響主執行緒的情況下,逐一把拷貝的資料寫成操作,記入重寫日誌。因為主執行緒未阻塞,仍然可以處理新來的操作。為了避免資料丟失,在AOF重寫過程中,新進入的寫命令會寫入到兩份日誌中。第一處日誌就正在使用的 AOF 日誌,Redis 會把這個操作寫到它的緩衝區。這樣一來,即使當機了,這個 AOF 日誌的操作仍然是齊全的,可以用於恢復。第二處日誌,就是指新的 AOF 重寫日誌。這個操作也會被寫到重寫日誌的緩衝區。這樣,重寫日誌也不會丟失最新的操作。等到拷貝資料的所有操作記錄重寫完成後,重寫日誌記錄的這些最新操作也會寫入新的 AOF 檔案,以保證資料庫最新狀態的記錄。此時,我們就可以用新的 AOF 檔案替代舊檔案了。
RDB快照檔案
由於AOF記錄的是操作命令,而不是實際的資料。所以,用AOF方法進行故障恢復的時候,需要逐一把操作日誌都執行一遍。如果操作日誌很多,那Redis恢復的就很慢,影響到正常使用。那有沒有即保證可靠性,還能在當機時實現快速的恢復辦法呢?那就是記憶體快照。對於Redis來說,它把某一時刻的狀態以檔案的形式寫到磁碟上。這樣一來,即使當機,快照檔案也不會丟失,資料的可靠性得到了保證。這個快照檔案就叫RDB(Redis DataBase)檔案。和AOF相比,RDB記錄的是某一時刻的資料,並不是操作,所以,在做資料恢復時,直接把RDB檔案載入到記憶體裡,很快的完成恢復。
Redis提供了兩個命令來生成RDB檔案,分別是save和bgsave。
- save:在主執行緒中執行,會導致阻塞。
- bgsave:建立一個子程式,專門用於寫入RDB檔案,避免主執行緒的阻塞。這也是redis生成RDB檔案的預設配置。
所以,我們可以採用bgsave來執行全量快照,既保證了可靠性,又避免了Redis的效能影響。接下了,我們來思考這麼一個問題,就是Redis在做全量快照時,Redis中的資料可以被修改嗎?Redis還支援寫操作嗎?為了快照而暫停寫操作,Redis肯定是不能接受的。所以Redis藉助了作業系統提供的寫時複製技術(Copy-On-Write,COW)。簡單來說,bgsave子程式是由主執行緒fork生成的,可以共享主執行緒的所有記憶體資料。bgsave執行之後,開始讀取主執行緒中的記憶體資料,並把他們寫入RDB檔案。此時,如果主執行緒對這些資料進行讀操作,則主執行緒和bgsave子程式相互不影響。但是,如果主執行緒執行寫操作或者修改操作,也就是修改記憶體中的一塊資料,那麼這塊資料會複製一份,生成副本。然後主執行緒在副本上進行修改。同時,bgsave子程式繼續把原來的資料寫入RDB檔案。這樣既保證了快照的完整性,也避免了對正常業務的影響。
接下來我們來看下一個問題,就是多久做一次快照,如果快照隔的時間太久,丟的資料就越多,間隔時間太短,丟失的資料越少,但是頻繁的執行全量快照會給磁碟帶來很大的壓力。由於fork建立子程式bgsave這個過程是需要阻塞主執行緒的,主執行緒的記憶體越大,fork時間越長。所以頻繁的fork出bgsave子程式,也就會頻繁阻塞主執行緒。那有什麼好的辦法既能利用RDB的快速恢復,又能以較小的開銷做到儘量少丟資料呢。Redis4.0提出了一個混合使用AOF日誌和RDB的方法。簡單來說,記憶體快照以一定的頻率執行,兩次快照之間使用AOF記錄操作命令。
最後總結一下,關於AOF和RDB的選擇問題,給大家提供3點建議。
- 資料不能丟失時,記憶體快照和 AOF 的混合使用是一個很好的選擇。
- 如果允許分鐘級別的資料丟失,可以只使用 RDB。
- 如果只用 AOF,優先使用 everysec 的配置選項,因為它在可靠性和效能之間取了一個平衡。
更多硬核知識,請關注公眾號”老韓隨筆"。