天天看點

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

     大家都知道Redis經常被使用在緩存的場景中,那有沒有想過這麼一個問題,一旦伺服器當機,記憶體中的資料全部丢失,我們該如何進行恢複呢?如果直接從後端資料庫恢複,不僅會給資料庫帶來巨大的壓力,還會使上層應用響應變慢。是以redis的持久化機制是很重要的。接下來我們一起來探讨一下Redis的持久化機制。目前Redis持久化主要有兩大機制,即AOF(Append Only File)日志和RDB快照。接下來我們就來分别學習一下。

 AOF日志

      AOF日志,即寫後日志,它的含義是Redis先執行指令,把資料寫入記憶體,然後再寫入日志。Redis為什麼要先執行指令後寫入日志呢?首先我們來看一下AOF日志裡記錄了什麼内容。AOF記錄的是Redis收到的每一條指令,這些日志以文本的形式儲存。假如我們執行了set hello world指令,AOF的内容如下:

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

     其中“*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檔案的記憶體緩沖區,由作業系統決定何時将緩沖區内容寫回磁盤。

      這三種寫回政策都無法做到兩全其美,都有自己的優缺點,我們隻能根據我們的業務場景,是需要高性能還是高可靠性來選擇不同的寫回政策。

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

      最後,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 檔案替代舊檔案了。

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

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檔案。這樣既保證了快照的完整性,也避免了對正常業務的影響。

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

      接下來我們來看下一個問題,就是多久做一次快照,如果快照隔的時間太久,丢的資料就越多,間隔時間太短,丢失的資料越少,但是頻繁的執行全量快照會給磁盤帶來很大的壓力。由于fork建立子程序bgsave這個過程是需要阻塞主線程的,主線程的記憶體越大,fork時間越長。是以頻繁的fork出bgsave子程序,也就會頻繁阻塞主線程。那有什麼好的辦法既能利用RDB的快速恢複,又能以較小的開銷做到盡量少丢資料呢。Redis4.0提出了一個混合使用AOF日志和RDB的方法。簡單來說,記憶體快照以一定的頻率執行,兩次快照之間使用AOF記錄操作指令。

最後總結一下,關于AOF和RDB的選擇問題,給大家提供3點建議。

  1. 資料不能丢失時,記憶體快照和 AOF 的混合使用是一個很好的選擇。
  2. 如果允許分鐘級别的資料丢失,可以隻使用 RDB。
  3. 如果隻用 AOF,優先使用 everysec 的配置選項,因為它在可靠性和性能之間取了一個平衡。

 更多硬核知識,請關注公衆号”老韓随筆"。

繼續閱讀