詳細分析Redis的持久化操作——RDB與AOF

特務依昂發表於2020-04-26

一、前言

  由於疫情的原因,學校還沒有開學,這也就讓我有了很多的時間。趁著時間比較多,我終於可以開始學習那些之前一直想學的技術了。最近這幾天開始學習Redis,買了本《Redis實戰》,看到了第四章,前三章都是講一些Redis的基本使用以及命令,第四章才開始涉及到原理相關的內容。《Redis實戰》的第四章涉及到了Redis的持久化、主從複製以及事務等內容,我個人認為這些應該屬於Redis中比較重要的部分,也是面試的常考內容。這篇部落格就來記錄一下Redis的持久化機制。


二、正文

2.1 為什麼需要持久化

  學習過Redis的應該都知道,RedisMySQL等關係型資料庫不同,它的資料不是儲存在硬碟中,而是存放在記憶體,所以Redis的速度非常快。而這也就會造成一個問題:電腦如果當機,或者由於某些原因需要重啟,此時記憶體中的資料就會丟失Redis既然把資料存放在記憶體,自然也就無法避免這個問題。所以,為了在電腦重啟後,能夠恢復原來的資料,Redis就需要提供持久化的機制,將Redis資料庫儲存在記憶體中的資料,在磁碟中進行備份,也就是持久化。而當Redis重啟後,去磁碟中將持久化的資料重新讀取到記憶體,便能避免資料的丟失。


2.2 Redis的持久化方式

  Redis提供了兩種持久化的方式,分別是:

  • 快照持久化;
  • AOF持久化;

  下面我就來詳細地介紹這兩種持久化的方式。


2.3 快照持久化(RDB)

  快照持久化也就做RDB持久化。快照持久化的實現方式簡單來說就是:Redis將當前記憶體中儲存的資料寫入到一個檔案中,將這個檔案作為Redis當前的一個快照,儲存在磁碟中。當Redis重啟時,將這個快照檔案中儲存的內容載入進記憶體,即可恢復Redis之前的狀態。預設情況下,Redis將快照儲存在一個叫做dump.rdb的檔案中,我們也可以在配置檔案中,通過dbfilename選項來設定快照檔案的名稱;預設情況下快照檔案就儲存在Redis的安裝目錄下,我們可以在配置檔案中,通過dir選項來配置快照檔案的儲存路徑(其實也是AOF的路徑)。


2.4 執行快照持久化的方式

  執行快照持久化有兩種方式:

2.4.1 使用配置檔案

  第一種方式就是在Redis的配置檔案中(windows下這個檔案叫redis.windows-service.conf)加上save配置項,比如像下面這樣:

save 60 100:在配置檔案中加上這一條的意思是,Redis會每60秒檢查一次,若在這60秒中,Redis資料庫執行了100次以上的寫操作,那Redis就會生成一個快照檔案,替換原來的快照檔案;若不滿足這個條件,則不生成快照,繼續等待60秒;

  我們可以根據自己的需求,調整每次等待的時間,以及對寫操作次數的要求。而且,我們可以在配置檔案中,新增多個save選項,比如一個save 60 100,一個save 5 10,則Redis5秒以及每100秒都會判斷一次。需要注意的是,我們不應該讓生成快照太過頻繁,因為這是一個比較消耗資源的工作,會降低Redis的響應速度。


2.4.2 使用指令

  執行快照持久化的第二個方法就是使用Redis指令,Redis提供了兩個指令來請求伺服器進行快照持久化,這兩個指令分別是SAVEBGSAVE

(a)BGSAVE指令

  BGSAVE的執行流程如下:

  1. Redis呼叫系統的fork(),建立出一個子程式;
  2. 子程式將當前Redis中的資料,寫入到一個臨時檔案中;同時父程式不受影響,繼續執行客戶端的請求;
  3. 子程式將所有的資料寫入到了臨時檔案後,於是使用這個檔案替換原來的快照檔案(預設是dump.rdb);

  值得一提的是,通過配置檔案執行快照持久化的方式,實際上就是Redis在判斷滿足條件時,呼叫BGSAVE指令來實現的。

(b)SAVE指令

  SAVE指令生成快照的方式與BGSAVE不同,Redis執行SAVE指令時,不會建立一個子程式,非同步的生成快照檔案,而是直接使用Redis當前程式。執行SAVE指令在建立快照的過程中,Redis伺服器會阻塞所有的Redis客戶端,直到快照生成完畢,並更新到磁碟之後,才會繼續執行客戶端發來的增刪改查的指令。

  當然,這個指令一般很少使用,因為會阻塞客戶端,造成停頓。但是實際上,這個指令的執行效率一般比BGSAVE更高,因為不需要建立子程式,而且在這個過程中,其他操作被阻塞,Redis伺服器一心一意地生成快照。在《Redis實戰》中,作者提到了使用SAVE指令的一個案例:

  當前伺服器的Redis資料庫中儲存了大量資料,使用BGSAVE指令生成快照會非常的耗時 ,而且由於所剩記憶體不多,甚至無法建立子程式,於是作者編寫了一個shell指令碼,這個指令碼的內容就是讓伺服器每天凌晨3點,執行SAVE命令,生成快照,這樣就不需要建立子程式,而且由於是凌晨三點,使用者較少,SAVE的阻塞機制也不會有太大的影響。


2.5 快照持久化的優缺點

(1)優點:

  1. 快照的rdb檔案是一個經過壓縮的緊湊檔案,它儲存了Redis在某個時間點上的資料集,這個檔案非常適合用來備份。我們可以儲存Redis伺服器在不同時間點上的rbd檔案,比如說一小時儲存一次,一個月儲存一次,這樣就可以在遇到問題或有特殊需求時,將Redis恢復到某一個時間點;

  2. RDB非常適用於災難恢復(disaster recovery):它只有一個檔案,並且內容都非常緊湊,可以(在加密後)將它傳送到別的伺服器上;

  3. RDB 可以最大化 Redis 的效能:父程式在儲存 RDB 檔案時唯一要做的就是 fork 出一個子程式,然後這個子程式就會處理接下來的所有儲存工作,父程式無須執行任何磁碟I/O操作,所以不會影響父程式處理客戶端的請求;

  4. RDB 在恢復大資料集時的速度比 AOF 的恢復速度要快。

(2)缺點

  1. 當我們的伺服器發生異常,導致停機時,那我們將會丟失最後一次建立快照後,所作的所有寫操作,因為這些操作發生在記憶體中,還沒來得及同步到磁碟。比如我們在配置檔案中配置每5分鐘生成一次快照,那當系統發生故障導致當機時,我們將會丟失好幾分鐘內的操作;
  2. 每次執行BGSAVE建立快照,都需要先建立出一個子程式,再由子程式執行後續操作,當記憶體中資料較大時,建立一個子程式將會非常耗時,造成伺服器等待數毫秒,甚至達到一秒,影響使用者體驗。而且從這一點也可以說明,我們不能通過提高生成快照的頻率,來解決第一個缺點;

2.6 AOF持久化

  AOF全稱為只追加檔案(append-only file),它的實現方式簡單來說就是:AOF持久化機制,會將Redis執行的所有寫指令,追加到到AOF的末尾,當伺服器發生當機,或者由於某些原因需要重啟時,在重啟後,讀取AOF檔案,重新執行其中記錄的寫操作,以此達到恢復資料的目的。

  AOF持久化機制預設是關閉的,我們可以在配置檔案中,配置appendonly yes來開啟。同時我們也可以通過配置appendfsync,控制寫操作追加到AOF中的頻率,它有如下三種選項:

  • alwaysRedis每次執行寫指令,都會立即將這個寫指令同步到AOF中;使用這個選項時,Redis發生異常,則最多隻會丟失一次寫操作(也就是在同步的過程中當機,沒同步完成),但是這也會導致Redis的響應速度變慢,因為此選項會造成頻繁的IO操作;
  • everysec(預設):每秒同步一次,使用此選項,速度足夠快,而且發生當機時也只會丟失1s內的寫操作;
  • no:讓作業系統來決定什麼時候進行同步,這個選項速度更快,但是不安全,當機時丟失資料的多少是不確定的;

  推薦(也是預設),使用第二個選項everysec,每秒進行一次同步,因為這個選項兼顧了速度與安全性,而第一個選項太慢,第三個選項無法保證安全性。


2.7 AOF的重寫機制

  AOF持久化的執行機制就是,不斷地將寫指令追加到AOF的末尾,這樣就會導致AOF越來越大。為了解決AOF越來越大的問題,Redis實現了一種優化機制——AOF重寫。當Redis檢查到AOF已經很大時,就會觸發重寫機制,優化其中的內容,將它優化為能夠得到相同結果的最小的指令集合。

  比如說,我們在Redis中使用SET指令建立了一個String型別的資料,最後使用DEL指令將它刪除了。如果開啟了AOF持久化,那麼則AOF中,將會記錄這條SETDEL指令。但是,在執行重寫的過程中,這個String最後被刪除了,那麼Redis就不會將這兩條指令加入新的AOF中,因為已經被刪除的資料,不需要恢復。再比如說,我們使用Redis維護了一個計數器cnt,我們使用了100INCR指令,讓cnt自增到了100,而Redis重寫AOF時,可以將這100incr修改為一條SET指令,直接將cnt設定為100,而不是保留100INCR

  經過了重寫後,AOF的大小將會大大減小,而且也去除了不必要的操作,優化了恢復資料的指令集。而AOF重寫的過程與生成一個快照檔案類似,如下:

  1. Redis執行fock(),建立一個子程式用來執行後續操作,而父程式繼續處理髮送到Redis的執行請求;
  2. 子程式重寫舊AOF檔案,將重寫後的內容寫入到一個臨時檔案;
  3. 如果這個過程中,有新的寫指令到達,那麼Redis會將這些寫指令依舊追加到舊的AOF中,同時也會將這些指令加入到記憶體的一個緩衝區中。這樣做的目的是,如果伺服器發生異常,AOF重寫失敗,這些指令依然能夠儲存在舊AOF,不會丟失;
  4. 當子程式完成重寫工作時,它給父程式傳送一個訊號,父程式在接收到訊號之後,將記憶體快取中的所有寫指令追加到新 AOF 檔案的末尾;
  5. 使用新AOF替換舊的AOF,這之後執行的所有寫指令都將追加到新的AOF中;

2.8 AOF的錯誤處理

  AOF檔案是有可能發生錯誤的,比如上面提過,如果當前Redis正在向AOF中追加一個寫指令,但是此時伺服器當機,那麼這個存入AOF中的這個寫指令就是不完整的,也就是AOF出現了錯誤。如果停機造成了 AOF 檔案出錯(corrupt), 那麼 Redis 在重啟時會拒絕載入這個 AOF 檔案, 從而確保資料的一致性不會被破壞。

  那麼,當AOF發生了錯誤,應該如何處理呢?我們可以使用如下命令:

redis-check-aof --fix:redis-check-aof用來檢測AOF是否存在錯誤,如果指定了--fix引數,那麼Redis在檢測到AOF的錯誤後,會對AOF進行修復。

  redis-check-aof修復AOF的方式非常簡單:掃描AOF檔案,尋找其中不正確或不完整的指令,當發現第一個出錯的指令後,就將這個指令以及這之後的所有指令刪除。為什麼需要刪除出錯指令之後的所有指令呢?因為當一條指令出錯,很有可能影響到後續的操作,導致後續操作的都是髒資料,而Redis無法檢測哪些指令是受到影響的,所以為了保險起見,就將後續指令全部刪除。不過不用擔心,因為在大多數情況下,出錯的都是AOF最末尾的指令。


2.9 AOF的優缺點

(1)優點:

  1. 使用 AOF 持久化會讓 Redis 變得非常耐久,意思就是說,當發生異常導致需要重啟伺服器時,只會丟失很少的一部分資料,因為AOF持久化預設1s同步一次,也就是說,Redis最多隻會丟失1s中所做的修改;
  2. Redis 可以在 AOF 檔案體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 檔案包含了恢復當前資料集所需的最小命令集合。
  3. AOF 檔案有序地儲存了對資料庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式儲存, 因此 AOF 檔案的內容非常容易被人讀懂, 對檔案進行分析(parse)也很輕鬆。
  4. AOF 檔案是一個只進行追加操作的日誌檔案(append only log), 因此對 AOF 檔案的寫入不需要進行 seek , 即使日誌因為某些原因而包含了未寫入完整的命令(比如寫入時磁碟已滿,寫入中途停機,等等), redis-check-aof 工具也可以輕易地修復這種問題。

(2)缺點:

  1. 對於相同的資料集來說,AOF 檔案的體積通常要大於快照RDB檔案的體積大,因為RDB只儲存資料,而AOF中的寫指令,既包含指令,也包含資料;
  2. 如果AOF體積太大,那麼恢復資料將要花費較長的時間,因為需要重做指令;

2.10 選擇快照還是AOF?

  說到這裡,可能就有人要問了,在實際生產中,我們應該使用快照持久化還是AOF持久化呢?

1、如果希望自己的資料庫有很高的安全性,則應該兩者同時使用,AOF用作精確記錄,而快照用作資料備份,前面也說過,快照非常適合用來做資料備份,因為它只儲存資料庫中的資料,並且經過了壓縮,而且它恢復Redis資料的速度一般要快於AOF

2、如果可以容忍一段時間內的資料丟失,則可以考慮只使用快照持久化,減小伺服器的開銷;

  值得一提的是,如果我們同時開啟了快照持久化和AOF持久化,Redis在重啟後,會優先選擇AOF來恢復資料,因為一般情況下,AOF能夠更加完整地恢復資料。


三、總結

  快照持久化和AOF持久化各有優劣,在實際生產環境中,我們一般是兩者配合使用,快照持久化消耗較低,而且適合用於備份,但是會丟失一段時間的資料;AOF持久化更加地耐久,可靠性更高,但是開銷可能相對較高。以上就對Redis的持久化機制做了一個比較詳細的介紹,相信看完只後,對Redis會有一個更加深入的理解。


四、參考

相關文章