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 的配置選項,因為它在可靠性和性能之間取了一個平衡。

 

 更多硬核知識,請關注公眾號”程序員學長"。

 

 

 

 

     

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM