Redis的持久化機制你學會了嗎?

     大家都知道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的三個可選值。

  1. Always,同步寫回:每個寫命令執行完,立馬同步地將日誌寫回磁盤;
  2. Everysec,每秒寫回:每個寫命令執行完,只是先把日誌寫到AOF文件的內存緩衝區,每隔一秒把緩衝區中的內容寫入磁盤;
  3. 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。

  1. save:在主線程中執行,會導致阻塞。
  2. 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點建議。

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

 

 更多硬核知識,請關注公眾號」老韓隨筆”。

 

 

 

 

     

 

Tags: