Redis持久化機制

  • 2020 年 4 月 13 日
  • 筆記

博客地址://tech101.cn/2020/03/05/Redis%E6%8C%81%E4%B9%85%E5%8C%96%E6%9C%BA%E5%88%B6

前言

Redis是一款純C語言編寫的符合ANSI C標準實現的內存數據庫。Redis以支持豐富的內存數據結構和高性能著稱,在互聯網行業中被廣泛用於緩存數據和內存計算。

雖然Redis是一款內存數據庫,但是它也提供了數據持久化的能力。本文,我們就來聊聊Redis的數據持久化機制。

持久化面臨的問題

在正式開始介紹Redis持久化功能之前,我們先來看下實現持久化能力需要面臨的一些技術問題。

當客戶端請求Redis服務端將數據寫入Redis數據庫的時候,數據將被存放在內存中。如果Redis數據庫啟用了持久化功能,那麼數據將被持久化到持久化設備(磁盤)上。從客戶端請求服務端寫入數據到數據被持久化到磁盤上,整個過程需要經歷如下幾個階段1

  1. 客戶端向服務端發起寫命令。
  2. 服務端接收到客戶端請求,執行寫命令將數據寫入內存。
  3. 服務端調用write()系統調用(Unix環境)將內存中的數據寫入內核緩衝區。
  4. 調用fsync()將內核緩衝區的數據寫入磁盤控制器的緩存中。
  5. 磁盤控制器將緩存中的數據寫入到磁盤的物理介質上。

在上面列出的5個步驟中,第1步到第3步數據都在內存中存放,一旦服務crash,那麼數據將永久性的丟失了。在第4步和第5步中,數據已經從內存轉移到了磁盤設備上,不過在第4步中數據是被寫入到了磁盤的控制器緩衝區(為了解決磁盤設備和內存設備訪問延遲的差異,通過緩衝區技術來提高單位時間內設備的吞吐量)中,所以一旦服務器掉電宕機,在緩存中的這部分數據也可能將會丟失(取決於物理存儲設備)。只有當數據經過第5步被寫入磁盤物理介質以後,數據才算真正地被保存了下來,不會因為服務器掉電而丟失數據。

從上面的過程中我們可以發現,為了保證數據持久化過程的順利,我們只有成功地將上面第1到5步這五個步驟同步執行完成以後,數據才算安全的被持久化下來,其中任意一步出現問題,數據都可能存在丟失的風險。

當然,要想完全的執行完上面的五個步驟是很理想的情況,實際在實現持久化機制的時候,將會面臨一些現實的約束。首先,完成上述五個步驟涉及到Redis服務、操作系統以及底層存儲硬件的緊密配合,對於操作系統之上的Redis服務實現者來說,要實現持久化功能,只能通過調用操作系統提供的功能(系統調用System call)來完成對底層存儲硬件的訪問,所以實現者能控制的只有上述的第1-4這四步,至於最後一步則可能不受Redis服務的實現者控制,由各個硬件設備自己實現(至少不能保證能提供對應的內核驅動API供操作系統訪問)。所以在持久化這件事上,實現者能做的是保證第1-4步能順利完成。

其次,第3步和第4步都需要調用系統調用,調用系統調用會導致進程用戶態和內核態的切換,這個過程是有性能損失的。頻繁的調用系統調用將會降低服務的性能,而Redis作為一款高性能的內存數據庫,服務的性能也是需要重點考慮的一個指標。所以為了平衡好數據安全性和性能,在實現持久化機制的時候需要作出取捨。

Redis的實現者在實現持久化的時候,為了兼顧性能和數據安全性,引入了兩種持久化方案:

  1. RDB持久化
  2. AOF持久化

下面,我們將介紹這兩種持久化方案。

RDB持久化

RDB持久化是Redis引入的一種數據安全性相對弱的持久化方案,通過異步將內存數據庫的快照寫入持久化文件來實現數據的持久化。由於生成快照是異步進行的,所以快照不會實時反應內存中的數據庫情況,因此它是一種數據安全性較弱的數據持久化方案。不過由於創建快照過程是異步進行的,在創建快照過程中基本不會對內存數據庫的操作產生影響,所以RDB持久化方案是一種注重性能,但是在數據安全性方面做出妥協的持久化方案。

下面,我們來看下RDB持久化的實現原理。

生成RDB快照

Redis提供了兩個命令:SAVEBGSAVE來創建RDB快照文件。這兩個命令的最大區別是:SAVE命令在生成RDB文件的時候會阻塞Redis的進程;而BGSAVE會創建一個子進程來生成RDB文件,不會阻塞服務器的進程。

127.0.0.1:6379> SAVE
OK

127.0.0.1:6379> BGSAVE
Background saving started

由於SAVE命令是通過阻塞服務器的進程來進行快照生成的,所以在生成快照期間服務器將拒絕來自服務器外部的請求。而BGSAVE命令通過創建子進程實現快照的生成,所以在生成快照期間Redis服務可以繼續執行客戶端的請求。不過需要注意的一點是:在BGSAVE命令執行期間,BGSAVESAVEBGREWRITEAOF命令的執行將會受到限制。

首先,在BGSAVE命令執行期間,SAVE命令會被拒絕執行;其次,在BGSAVE命令執行完成前,新的BGSAVE命令也會被拒絕執行;最後,對於AOF重新命令BGREWRITEAOF,在BGSAVE命令執行期間,該命令將會被延後執行。同時,如果在BGSAVE命令執行之前有BGREWRITEAOF命令正在執行,則BGSAVE命令也需要等到AOF重新命令完成以後才能被執行。這麼做是考慮到持久化是一個消耗IO資源的操作,雖然BGSAVEBGREWRITEAOF兩個命令都是通過創建子進程來執行的,但是出於服務器性能的考慮,這兩個命令不能同時執行。

定時生成快照

除了通過命令生成RDB快照以外,Redis也支持定時生成快照的功能。通過在Redis配置文件中設置快照生成配置,服務器可以在運行過程中自動生成RDB快照。自動生成快照和BGSAVE命令執行的效果類似,也是通過創建子進程的方式生成RDB快照文件。

用戶可以通過在配置文件中設置save選項的值來控制自動生成快照的頻率。如果存在多個save選項配置,則任意一個配置滿足條件都會觸發生成RDB快照。

save 900 1
save 300 10
save 60 10000

上述配置的三個條件,只要滿足下面任意一個條件,快照就會被創建:

  1. 服務器在900秒內,內存數據庫發生了至少1次修改。
  2. 服務器在300秒內,內存數據庫發生了至少10次修改。
  3. 服務器在60秒內,內存數據庫發送了至少10000次修改。

載入RDB快照

當Redis服務器啟動的時候如果發現存在RDB快照文件,則會進行RDB快照文件的載入。在RDB快照載入期間,Redis服務將會處於阻塞狀態,直到快照載入完成。

1:M 12 Apr 2020 07:29:28.289 # Server initialized
1:M 12 Apr 2020 07:29:28.291 * DB loaded from disk: 0.001 seconds
...

RDB文件結構

通過RDB持久化創建的快照文件是一個由5部分組成的二進制文件。

  1. redis快照文件以REDIS字符串開頭,佔用5個位元組(C語言中一個char類型佔據1個位元組)。用於在載入的時候檢查文件是否是RDB快照文件。
  2. db_version的長度為4個位元組,是一個字符串表示的整數,記錄了RDB文件的版本號。
  3. databases是一個變長的字段,存放了Redis服務中的數據庫數據。如果Redis數據庫為空,則這個字段的長度為0。
  4. EOF是長度為1個位元組的常量,用於標識RDB文件正文內容的結束。
  5. check_sum是一個長度為8個位元組的無符號整數,保存了前四部分的校驗和,用於在載入RDB文件的時候進行文件完整性檢查。

關於RDB文件格式的詳細細節可以參考《Redis設計與實現》一書,作者對RDB文件格式做了詳盡的介紹。

優缺點

優點

  1. 生成RDB快照文件的過程是異步的,所以在持久化過程中對服務器性能影響小。
  2. RDB文件存儲的是內存數據庫的快照,採用緊湊的二進制文件存儲。通過RDB文件進行數據庫恢復的時候速度快。

缺點

  1. 由於RDB文件是異步進行備份的,所以存在數據安全性弱的弊端:當系統發生故障導致內存數據庫數據丟失的時候,從RDB文件中只能恢復創建RDB快照那一刻的數據,在最近一次創建RDB快照那一刻到服務器宕機之間的數據將永久性的丟失了。數據恢復的完整程度依賴於RDB快照創建的頻率。
  2. 由於RDB快照是將整個內存數據庫備份下來,所以當內存數據庫很大的時候創建RDB文件需要耗費更久的時間。

AOF持久化

鑒於RDB持久化方案存在的一些問題,Redis提供了另外一種持久化機制:AOF(Append Only File)持久化功能。

不同於RDB持久化方案通過創建快照文件來持久化數據,AOF持久化方案通過持久化發送到Redis服務器的寫命令來實現持久化功能。AOF持久化機制會把所有引起內存數據庫數據變化的寫命令都保存下來,通過文件追加(Append)的方式保存到AOF文件中。在通過AOF文件進行數據恢復的時候我們可以通過重放AOF文件中的命令來恢復出Redis內存數據庫的內容,這就是AOF機制能進行持久化的原理。

AOF持久化方案通過犧牲一定的性能來換取數據的安全性,以滿足對數據安全性要求較高的場景。

 

創建AOF文件

當Redis服務器啟用了AOF持久化選項以後,服務器會將接收到的寫命令以追加的方式寫入服務器的AOF緩衝區,然後由服務器按照不同的AOF持久化選項以不同的策略將緩衝區的命令寫入AOF文件。

AOF持久化選項

以追加方式進行文件寫入本質上是一種順序訪問磁盤的方式。對於磁盤這種存儲介質來說,順序訪問比隨機訪問的性能會高很多,所以追加方式寫入磁盤對服務的性能影響較小。但是這種性能的消耗在有些場景下可能是不能接受的,所以為了兼顧數據安全性和性能,Redis的AOF持久化方案提供了一些持久化選項。

Redis通過設置appendfsync選項來控制持久化的數據安全程度。該參數提供了三個可選的AOF持久化選項值:alwayseverysecno。分別對應不同的數據安全級別。

選項值 作用
always 每執行一次命令就進行AOF文件同步
everysec 每隔一秒進行一次AOF文件同步。由於同步AOF文件是阻塞操作,所以當前一次同步操作耗時超過1秒的時候,下一次同步操作將會到等上一次同步完成以後才進行,因此最差情況下會造成延遲2秒的同步
no Redis不主動進行AOF文件同步,而是交給操作系統定時進行文件同步

注意:如果appendfsync選項的值沒有配置,則默認值為everysec

我們在第一節中討論持久化面臨的問題的時候,提到服務器能控制的持久化步驟為第1-4步。其中第4步通過fsync()命令將內核緩衝區中的數據刷新到磁盤中(Linux環境)。Linux環境下的Redis服務器就是通過該系統調用來實現appendfsync的不同持久化選項的。

由於每次進行fsync()系統調用相對比較耗時,所以AOF持久化方案提供了上述三種選項供開發者選擇,以滿足對數據安全性要求不同的使用場景。

如果appendfsync選項的值被配置為always,那麼數據安全性最高,但是服務的性能會受到影響;如果選項值設置為no,那麼數據安全性的保證相對較弱,但是服務器的性能有所提高;而everysec則是這兩種場景的一個折中。

從AOF文件恢復

和RDB快照文件直接包含數據庫狀態不同,AOF文件包含了Redis服務器收到的所有寫命令,所以當服務器從AOF文件恢複數據庫的時候,需要對AOF文件中的所有命令按序進行重放來還原出數據庫狀態。

在Redis服務器通過AOF文件恢複數據庫的時候,為AOF文件創建一個偽客戶端,然後通過這個偽客戶端來執行AOF文件中的命令,以此來重建數據庫。

重寫AOF文件

由於AOF文件恢復需要重放AOF文件中的所有命令,所以數據庫的恢復時間和AOF文件的大小成正比。當AOF文件很大的時候恢復過程需要耗費很長一段時間才能完成,而RDB由於存儲的是快照,所以沒有這方面的困擾,不過RDB快照恢復的速度和數據庫的大小正相關。

Redis為了解決AOF文件太大的情況,提供了AOF文件重寫的功能。通過對AOF文件進行重寫,將一些命令進行合併來達到縮減AOF文件的目的,最終實現減少AOF文件恢復時間的目的。

比如,通過對下面的這幾個命令進行重寫,產生一個重寫後的命令來達到和重寫前同樣的效果:

# 重寫前
PUSH list "A"
PUSH list "B"

# 重寫後
PUSH list "A" "B"

Redis提供了BGREWRITEAOF命令進行AOF重寫操作。BGREWRITEAOF命令是一個後台執行的命令,通過創建一個子進程來完成AOF文件重寫工作。

重寫過程

在AOF文件重寫過程中Redis服務器還可以繼續處理請求,所以AOF文件會繼續追加命令,如果不對AOF重寫和命令追加進行協調,那麼將導致AOF文件數據不一致的情況。

為了解決這個問題,Redis服務器會在AOF文件重寫開始以後創建一個AOF重寫緩衝區。當服務器接收到寫命令以後,會同步將這個命令寫入AOF重寫緩衝區和原先的AOF追加緩衝區。這保證了在AOF重寫期間,Redis服務器可以繼續進行AOF持久化,而且新接受到的命令也會被記錄到AOF重寫緩衝區中。

當處理AOF重寫的子進程完成AOF重寫工作以後,會通過信號機制通知父進程(服務器進程),父進程接收到信號以後會執行如下兩步操作:

  1. 服務器進程會將AOF重寫緩衝區中的命令寫入重寫後的新AOF文件中。
  2. 服務器會原子的將AOF文件修改為重寫後的AOF文件,完成新舊AOF文件的替換。

優缺點

優點

  1. 數據的安全性更高,當服務crash以後丟失的數據更少。
  2. AOF文件的可讀性更好。
  3. 通過append方式追加命令,訪問磁盤的效率高。

缺點

  1. 由於AOF持久化會在一定程度上進行磁盤同步處理操作,這個過程是阻塞的(雖然很短),所以對服務器處理命令的性能會產生影響。
  2. AOF文件記錄的是寫命令,恢復的時候需要重放命令來得到內存數據庫的狀態,即使有AOF重寫機制,恢復速度上和RDB相比也會有差距。

從RDB持久化轉換成AOF持久化

在調整持久化方案的時候,Redis在不同版本有不同的操作方式。在Redis 2.2以後的版本中可以在不停機的情況下修改持久化方案,通過如下步驟進行操作:

  1. 創建最近的RDB文件的備份。
  2. 將備份保存在安全的位置。
  3. 發起如下命令:
    • 客戶端執行 config set appendonly yes啟用AOF持久化。
    • 客戶端執行 config set save 「」關閉RDB持久化。
  4. 確認數據庫包含相同的keys。
  5. 確認write操作被正確追加到了AOF文件。
  6. 修改redis.conf文件,保證下次重啟的時候也是正確的持久化配置。

同時使用AOF持久化和RDB持久化

在AOF和RDB兩個持久化方案都啟動的情況下,除了RDB的快照命令SAVE會阻塞進程導致其他命令都不能被執行之外,BGSAVEBGREWRITEAOF命令都是在子進程中異步執行的,不會影響到服務器進程的正常執行。不過考慮到持久化操作會佔用磁盤IO資源,對機器的IO性能會產生影響,Redis對BGSAVEBGREWRITEAOF命令的執行做了限制:同一時刻服務中只能執行一個命令。

同樣,在AOF和RDB這兩個持久化方案都啟用的情況下恢複數據的時候,由於AOF文件的數據安全性(完整性)更高,Redis在啟動的時候會優先採用AOF文件恢復的方式重建數據庫,只有當AOF功能關閉的情況下才會使用RDB進行數據重建。

總結

在本文中,我們介紹了Redis的兩種持久化方案,分析了兩種方案各自的實現原理和優缺點。