完全掌握Redis持久化:RDB和AOF

2022-06-16 14:01:25
本篇文章給大家帶來了關於的相關知識,其中主要介紹了關於持久化的相關問題,包括了為什麼需要持久化、RDB持久化、AOF持久化等等內容,下面一起來看一下,希望對大家有幫助。

推薦學習:

一、為什麼需要持久化?

Redis對資料的操作都是基於記憶體的,當遇到了程序退出、伺服器宕機等意外情況,如果沒有持久化機制,那麼Redis中的資料將會丟失無法恢復。有了持久化機制,Redis在下次重新啟動時可以利用之前持久化的檔案進行資料恢復。Redis支援的兩種持久化機制:

RDB:把當前資料生成快照儲存在硬碟上。
AOF:記錄每次對資料的操作到硬碟上。

二、RDB持久化

在指定的時間間隔內將記憶體中的資料集快照寫入磁碟,它恢復時是將快照檔案直接讀到記憶體裡。RDB(Redis DataBase)持久化是把當前Redis中全部資料生成快照儲存在硬碟上。RDB持久化可以手動觸發,也可以自動觸發。

1、備份是如何執行的?

redis會單獨建立(fork)一個子程序來進行持久化,會先將資料寫入到臨時檔案中,待持久化過程都結束了,再用這個臨時檔案替換上次持久化好了的檔案。整個過程中,主程序是不進行任何IO操作的,這就確保了極高的效能。如果需要進行大規模資料的恢復,且對資料的恢復完整性不是非常敏感,那麼RDB方式要比AOF方式更加的高效。RDB的缺點是最後一次持久化的資料可能丟失。

2、RDB持久化流程

在這裡插入圖片描述

3、手動觸發

savebgsave命令都可以手動觸發RDB持久化。

  1. save
    執行save命令會手動觸發RDB持久化,但是save命令會阻塞Redis服務,直到RDB持久化完成。當Redis服務儲存大量資料時,會造成較長時間的阻塞,不建議使用。
  2. bgsave
    執行bgsave命令也會手動觸發RDB持久化,和save命令不同是:Redis服務一般不會阻塞。Redis程序會執行fork操作建立子程序,RDB持久化由子程序負責,不會阻塞Redis服務程序。Redis服務的阻塞只發生在fork階段,一般情況時間很短。
    bgsave命令的具體流程如下圖:
    在這裡插入圖片描述
    1、執行bgsave命令,Redis程序先判斷當前是否存在正在執行的RDB或AOF子執行緒,如果存在就是直接結束。
    2、Redis程序執行fork操作建立子執行緒,在fork操作的過程中Redis程序會被阻塞。
    3、Redis程序fork完成後,bgsave命令就結束了,自此Redis程序不會被阻塞,可以響應其他命令。
    4、子程序根據Redis程序的記憶體生成快照檔案,並替換原有的RDB檔案。
    5、同時傳送訊號給主程序,通知主程序rdb持久化完成,主程序更新相關的統計資訊(info Persitence下的rdb_*相關選項)。

4、自動觸發

除了執行以上命令手動觸發以外,Redis內部可以自動觸發RDB持久化。自動觸發的RDB持久化都是採用bgsave的方式,減少Redis程序的阻塞。那麼,在什麼場景下會自動觸發呢?

  1. 在組態檔中設定了save的相關設定,如sava m n,它表示在m秒內資料被修改過n次時,自動觸發bgsave操作。
  2. 當從節點做全量複製時,主節點會自動執行bgsave操作,並且把生成的RDB檔案傳送給從節點。
  3. 執行debug reload命令時,也會自動觸發bgsave操作。
  4. 執行shutdown命令時,如果沒有開啟AOF持久化也會自動觸發bgsave操作。

5、RDB優點

RDB檔案是一個緊湊的二進位制壓縮檔案,是Redis在某個時間點的全部資料快照。所以使用RDB恢復資料的速度遠遠比AOF的快,非常適合備份、全量複製、災難恢復等場景。

6、RDB缺點

每次進行bgsave操作都要執行fork操作建立子經常,屬於重量級操作,頻繁執行成本過高,所以無法做到實時持久化,或者秒級持久化。

另外,由於Redis版本的不斷迭代,存在不同格式的RDB版本,有可能出現低版本的RDB格式無法相容高版本RDB檔案的問題。

7、dump.rdb中設定RDB

快照週期:記憶體快照雖然可以通過技術人員手動執行SAVEBGSAVE命令來進行,但生產環境下多數情況都會設定其週期性執行條件。

  • Redis中預設的週期新設定
# 週期性執行條件的設定格式為
save <seconds> <changes>

# 預設的設定為:
save 900 1
save 300 10
save 60 10000

# 以下設定方式為關閉RDB快照功能
save ""

以上三項預設資訊設定代表的意義是:

  • 如果900秒內有1條Key資訊發生變化,則進行快照;
  • 如果300秒內有10條Key資訊發生變化,則進行快照;
  • 如果60秒內有10000條Key資訊發生變化,則進行快照。讀者可以按照這個規則,根據自己的實際請求壓力進行設定調整。
  • 其它相關設定
# 檔名稱
dbfilename dump.rdb

# 檔案儲存路徑
dir ./

# 如果持久化出錯,主程序是否停止寫入
stop-writes-on-bgsave-error yes

# 是否壓縮
rdbcompression yes

# 匯入時是否檢查
rdbchecksum yes
  • dbfilename:RDB檔案在磁碟上的名稱。
  • dir:RDB檔案的儲存路徑。預設設定為「./」,也就是Redis服務的主目錄。
  • stop-writes-on-bgsave-error:上文提到的在快照進行過程中,主程序照樣可以接受使用者端的任何寫操作的特性,是指在快照操作正常的情況下。如果快照操作出現異常(例如作業系統使用者許可權不夠、磁碟空間寫滿等等)時,Redis就會禁止寫操作。這個特性的主要目的是使運維人員在第一時間就發現Redis的執行錯誤,並進行解決。一些特定的場景下,您可能需要對這個特性進行設定,這時就可以調整這個引數項。該引數項預設情況下值為yes,如果要關閉這個特性,指定即使出現快照錯誤Redis一樣允許寫操作,則可以將該值更改為no。
  • rdbcompression:該屬性將在字串型別的資料被快照到磁碟檔案時,啟用LZF壓縮演演算法。Redis官方的建議是請保持該選項設定為yes,因為「it’s almost always a win」。
  • rdbchecksum:從RDB快照功能的version 5 版本開始,一個64位元的CRC冗餘校驗編碼會被放置在RDB檔案的末尾,以便對整個RDB檔案的完整性進行驗證。這個功能大概會多損失10%左右的效能,但獲得了更高的資料可靠性。所以如果您的Redis服務需要追求極致的效能,就可以將這個選項設定為no。

8、 RDB 更深入理解

  • 由於生產環境中我們為Redis開闢的記憶體區域都比較大(例如6GB),那麼將記憶體中的資料同步到硬碟的過程可能就會持續比較長的時間,而實際情況是這段時間Redis服務一般都會收到資料寫操作請求。那麼如何保證資料一致性呢?
    RDB中的核心思路是Copy-on-Write,來保證在進行快照操作的這段時間,需要壓縮寫入磁碟上的資料在記憶體中不會發生變化。在正常的快照操作中,一方面Redis主程序會fork一個新的快照程序專門來做這個事情,這樣保證了Redis服務不會停止對使用者端包括寫請求在內的任何響應。另一方面這段時間發生的資料變化會以副本的方式存放在另一個新的記憶體區域,待快照操作結束後才會同步到原來的記憶體區域。
    舉個例子:如果主執行緒對這些資料也都是讀操作(例如圖中的鍵值對 A),那麼,主執行緒和 bgsave 子程序相互不影響。但是,如果主執行緒要修改一塊資料(例如圖中的鍵值對 C),那麼,這塊資料就會被複制一份,生成該資料的副本。然後,bgsave 子程序會把這個副本資料寫入 RDB 檔案,而在這個過程中,主執行緒仍然可以直接修改原來的資料。
    在這裡插入圖片描述
  • 在進行快照操作的這段時間,如果發生服務崩潰怎麼辦?
    很簡單,在沒有將資料全部寫入到磁碟前,這次快照操作都不算成功。如果出現了服務崩潰的情況,將以上一次完整的RDB快照檔案作為恢復記憶體資料的參考。也就是說,在快照操作過程中不能影響上一次的備份資料。Redis服務會在磁碟上建立一個臨時檔案進行資料操作,待操作成功後才會用這個臨時檔案替換掉上一次的備份。
  • 可以每秒做一次快照嗎?
    對於快照來說,所謂「連拍」就是指連續地做快照。這樣一來,快照的間隔時間變得很短,即使某一時刻發生宕機了,因為上一時刻快照剛執行,丟失的資料也不會太多。但是,這其中的快照間隔時間就很關鍵了。
    如下圖所示,我們先在 T0 時刻做了一次快照,然後又在 T0+t 時刻做了一次快照,在這期間,資料塊 5 和 9 被修改了。如果在 t 這段時間內,機器宕機了,那麼,只能按照 T0 時刻的快照進行恢復。此時,資料塊 5 和 9 的修改值因為沒有快照記錄,就無法恢復了。
    在這裡插入圖片描述

針對RDB不適合實時持久化的問題,Redis提供了AOF持久化方式來解決

三、AOF持久化

AOF(Append Only File)持久化是把每次寫命令追加寫入紀錄檔中,當需要恢復資料時重新執行AOF檔案中的命令就可以了。AOF解決了資料持久化的實時性,也是目前主流的Redis持久化方式。

Redis是「寫後」紀錄檔,Redis先執行命令,把資料寫入記憶體,然後才記錄紀錄檔。紀錄檔裡記錄的是Redis收到的每一條命令,這些命令是以文字形式儲存。PS: 大多數的資料庫採用的是寫前紀錄檔(WAL),例如MySQL,通過寫前紀錄檔和兩階段提交,實現資料和邏輯的一致性。

而AOF紀錄檔採用寫後紀錄檔,即先寫記憶體,後寫紀錄檔
在這裡插入圖片描述
為什麼採用寫後紀錄檔?
Redis要求高效能,採用寫紀錄檔有兩方面好處:

  • 避免額外的檢查開銷:Redis 在向 AOF 裡面記錄紀錄檔的時候,並不會先去對這些命令進行語法檢查。所以,如果先記紀錄檔再執行命令的話,紀錄檔中就有可能記錄了錯誤的命令,Redis 在使用紀錄檔恢復資料時,就可能會出錯。
  • 不會阻塞當前的寫操作

但這種方式存在潛在風險:

  • 如果命令執行完成,寫紀錄檔之前宕機了,會丟失資料。
  • 主執行緒寫磁碟壓力大,導致寫盤慢,阻塞後續操作。

1、如何實現AOF?

AOF紀錄檔記錄Redis的每個寫命令,步驟分為:命令追加(append)、檔案寫入(write)和檔案同步(sync)。

  • 命令追加 當AOF持久化功能開啟了,伺服器在執行完一個寫命令之後,會以協定格式將被執行的寫命令追加到伺服器的 aof_buf 緩衝區。
  • 檔案寫入和同步 關於何時將 aof_buf 緩衝區的內容寫入AOF檔案中,Redis提供了三種寫回策略:
    在這裡插入圖片描述
  • Always,同步寫回:每個寫命令執行完,立馬同步地將紀錄檔寫回磁碟;
  • Everysec,每秒寫回:每個寫命令執行完,只是先把紀錄檔寫到AOF檔案的記憶體緩衝區,每隔一秒把緩衝區中的內容寫入磁碟;
  • No,作業系統控制的寫回:每個寫命令執行完,只是先把紀錄檔寫到AOF檔案的記憶體緩衝區,由作業系統決定何時將緩衝區內容寫回磁碟。

2、redis.conf中設定AOF

預設情況下,Redis是沒有開啟AOF的,可以通過設定redis.conf檔案來開啟AOF持久化,關於AOF的設定如下:

# appendonly引數開啟AOF持久化
appendonly no

# AOF持久化的檔名,預設是appendonly.aof
appendfilename "appendonly.aof"

# AOF檔案的儲存位置和RDB檔案的位置相同,都是通過dir引數設定的
dir ./

# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no

# aof重寫期間是否同步
no-appendfsync-on-rewrite no

# 重寫觸發設定
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# 載入aof出錯如何處理
aof-load-truncated yes

# 檔案重寫策略
aof-rewrite-incremental-fsync yes

以下是Redis中關於AOF的主要設定資訊:
appendfsync:這個引數項是AOF功能最重要的設定項之一,主要用於設定「真正執行」操作命令向AOF檔案中同步的策略。

什麼叫「真正執行」呢?還記得Linux作業系統對磁碟裝置的操作方式嗎? 為了保證作業系統中I/O佇列的操作效率,應用程式提交的I/O操作請求一般是被放置在linux Page Cache中的,然後再由Linux作業系統中的策略自行決定正在寫到磁碟上的時機。而Redis中有一個fsync()函數,可以將Page Cache中待寫的資料真正寫入到物理裝置上,而缺點是頻繁呼叫這個fsync()函數干預作業系統的既定策略,可能導致I/O卡頓的現象頻繁 。

與上節對應,appendfsync引數項可以設定三個值,分別是:always、everysec、no,預設的值為everysec。

no-appendfsync-on-rewrite:always和everysec的設定會使真正的I/O操作高頻度的出現,甚至會出現長時間的卡頓情況,這個問題出現在作業系統層面上,所有靠工作在作業系統之上的Redis是沒法解決的。為了儘量緩解這個情況,Redis提供了這個設定項,保證在完成fsync函數呼叫時,不會將這段時間內發生的命令操作放入作業系統的Page Cache(這段時間Redis還在接受使用者端的各種寫操作命令)。

auto-aof-rewrite-percentage:上文說到在生產環境下,技術人員不可能隨時隨地使用「BGREWRITEAOF」命令去重寫AOF檔案。所以更多時候我們需要依靠Redis中對AOF檔案的自動重寫策略。Redis中對觸發自動重寫AOF檔案的操作提供了兩個設定:
auto-aof-rewrite-percentage表示如果當前AOF檔案的大小超過了上次重寫後AOF檔案的百分之多少後,就再次開始重寫AOF檔案。例如該引數值的預設設定值為100,意思就是如果AOF檔案的大小超過上次AOF檔案重寫後的1倍,就啟動重寫操作。
auto-aof-rewrite-min-size:設定項表示啟動AOF檔案重寫操作的AOF檔案最小大小。如果AOF檔案大小低於這個值,則不會觸發重寫操作。注意,auto-aof-rewrite-percentage和auto-aof-rewrite-min-size只是用來控制Redis中自動對AOF檔案進行重寫的情況,如果是技術人員手動呼叫「BGREWRITEAOF」命令,則不受這兩個限制條件左右。

3、深入理解AOF重寫

AOF會記錄每個寫命令到AOF檔案,隨著時間越來越長,AOF檔案會變得越來越大。如果不加以控制,會對Redis伺服器,甚至對作業系統造成影響,而且AOF檔案越大,資料恢復也越慢。為了解決AOF檔案體積膨脹的問題,Redis提供AOF檔案重寫機制來對AOF檔案進行「瘦身」。

圖例解釋AOF重寫
在這裡插入圖片描述
AOF重寫會阻塞嗎?
AOF重寫過程是由後臺程序bgrewriteaof來完成的。主執行緒fork出後臺的bgrewriteaof子程序,fork會把主執行緒的記憶體拷貝一份給bgrewriteaof子程序,這裡面就包含了資料庫的最新資料。然後,bgrewriteaof子程序就可以在不影響主執行緒的情況下,逐一把拷貝的資料寫成操作,記入重寫紀錄檔。所以aof在重寫時,在fork程序時是會阻塞住主執行緒的。

AOF紀錄檔何時會重寫?
有兩個設定項控制AOF重寫的觸發:
auto-aof-rewrite-min-size:表示執行AOF重寫時檔案的最小大小,預設為64MB。
auto-aof-rewrite-percentage:這個值的計算方式是,當前aof檔案大小和上一次重寫後aof檔案大小的差值,再除以上一次重寫後aof檔案大小。也就是當前aof檔案比上一次重寫後aof檔案的增量大小,和上一次重寫後aof檔案大小的比值。

重寫紀錄檔時,有新資料寫入咋整?
重寫過程總結為:「一個拷貝,兩處紀錄檔」。在fork出子程序時的拷貝,以及在重寫時,如果有新資料寫入,主執行緒就會將命令記錄到兩個aof紀錄檔記憶體緩衝區中。如果AOF寫回策略設定的是always,則直接將命令寫回舊的紀錄檔檔案,並且儲存一份命令至AOF重寫緩衝區,這些操作對新的紀錄檔檔案是不存在影響的。(舊的紀錄檔檔案:主執行緒使用的紀錄檔檔案,新的紀錄檔檔案:bgrewriteaof程序使用的紀錄檔檔案)

而在bgrewriteaof子程序完成紀錄檔檔案的重寫操作後,會提示主執行緒已經完成重寫操作,主執行緒會將AOF重寫緩衝中的命令追加到新的紀錄檔檔案後面。這時候在高並行的情況下,AOF重寫緩衝區積累可能會很大,這樣就會造成阻塞,Redis後來通過Linux管道技術讓aof重寫期間就能同時進行回放,這樣aof重寫結束後只需回放少量剩餘的資料即可。最後通過修改檔名的方式,保證檔案切換的原子性。

在AOF重寫紀錄檔期間發生宕機的話,因為紀錄檔檔案還沒切換,所以恢復資料時,用的還是舊的紀錄檔檔案。

總結操作:

  • 主執行緒fork出子程序重寫aof紀錄檔
  • 子程序重寫紀錄檔完成後,主執行緒追加aof紀錄檔緩衝
  • 替換紀錄檔檔案

溫馨提示

這裡的程序和執行緒的概念有點混亂。因為後臺的bgreweiteaof程序就只有一個執行緒在操作,而主執行緒是Redis的操作程序,也是單獨一個執行緒。這裡想表達的是Redis主程序在fork出一個後臺程序之後,後臺程序的操作和主程序是沒有任何關聯的,也不會阻塞主執行緒

在這裡插入圖片描述
主執行緒fork出子程序是如何複製記憶體資料的?
fork採用作業系統提供的寫時複製(copy on write)機制,就是為了避免一次性拷貝大量記憶體資料給子程序造成阻塞。fork子程序時,子程序時會拷貝父程序的頁表,即虛實對映關係(虛擬記憶體和實體記憶體的對映索引表),而不會拷貝實體記憶體。這個拷貝會消耗大量cpu資源,並且拷貝完成前會阻塞主執行緒,阻塞時間取決於記憶體中的資料量,資料量越大,則記憶體頁表越大。拷貝完成後,父子程序使用相同的記憶體地址空間。

但主程序是可以有資料寫入的,這時候就會拷貝實體記憶體中的資料。如下圖(程序1看做是主程序,程序2看做是子程序):
在這裡插入圖片描述
在主程序有資料寫入時,而這個資料剛好在頁c中,作業系統會建立這個頁面的副本(頁c的副本),即拷貝當前頁的物理資料,將其對映到主程序中,而子程序還是使用原來的的頁c。

在重寫紀錄檔整個過程時,主執行緒有哪些地方會被阻塞?

  • fork子程序時,需要拷貝虛擬頁表,會對主執行緒阻塞。
  • 主程序有bigkey寫入時,作業系統會建立頁面的副本,並拷貝原有的資料,會對主執行緒阻塞。
  • 子程序重寫紀錄檔完成後,主程序追加aof重寫緩衝區時可能會對主執行緒阻塞。

為什麼AOF重寫不復用原AOF紀錄檔?

  • 父子程序寫同一個檔案會產生競爭問題,影響父程序的效能。
  • 如果AOF重寫過程中失敗了,相當於汙染了原本的AOF檔案,無法做恢復資料使用。

三、RDB和AOF混合方式(4.0版本)

Redis 4.0 中提出了一個混合使用 AOF 紀錄檔和記憶體快照的方法。簡單來說,記憶體快照以一定的頻率執行,在兩次快照之間,使用 AOF 紀錄檔記錄這期間的所有命令操作。

這樣一來,快照不用很頻繁地執行,這就避免了頻繁 fork 對主執行緒的影響。而且,AOF 紀錄檔也只用記錄兩次快照間的操作,也就是說,不需要記錄所有操作了,因此,就不會出現檔案過大的情況了,也可以避免重寫開銷。

如下圖所示,T1 和 T2 時刻的修改,用 AOF 紀錄檔記錄,等到第二次做全量快照時,就可以清空 AOF 紀錄檔,因為此時的修改都已經記錄到快照中了,恢復時就不再用紀錄檔了。
在這裡插入圖片描述
這個方法既能享受到 RDB 檔案快速恢復的好處,又能享受到 AOF 只記錄操作命令的簡單優勢, 實際環境中用的很多。

四、從持久化中恢復資料

資料的備份、持久化做完了,我們如何從這些持久化檔案中恢復資料呢?如果一臺伺服器上有既有RDB檔案,又有AOF檔案,該載入誰呢?

其實想要從這些檔案中恢復資料,只需要重新啟動Redis即可。我們還是通過圖來了解這個流程:
在這裡插入圖片描述

  • redis重新啟動時判斷是否開啟aof,如果開啟了aof,那麼就優先載入aof檔案;
  • 如果aof存在,那麼就去載入aof檔案,載入成功的話redis重新啟動成功,如果aof檔案載入失敗,那麼會列印紀錄檔表示啟動失敗,此時可以去修復aof檔案後重新啟動;
  • 若aof檔案不存在,那麼redis就會轉而去載入rdb檔案,如果rdb檔案不存在,redis直接啟動成功;
  • 如果rdb檔案存在就會去載入rdb檔案恢復資料,如載入失敗則列印紀錄檔提示啟動失敗,如載入成功,那麼redis重新啟動成功,且使用rdb檔案恢復資料;

那麼為什麼會優先載入AOF呢?因為AOF儲存的資料更完整,通過上面的分析我們知道AOF基本上最多損失1s的資料。

推薦學習:

以上就是完全掌握Redis持久化:RDB和AOF的詳細內容,更多請關注TW511.COM其它相關文章!