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:
- 客戶端向服務端發起寫命令。
- 服務端接收到客戶端請求,執行寫命令將數據寫入記憶體。
- 服務端調用
write()
系統調用(Unix環境)將記憶體中的數據寫入內核緩衝區。 - 調用
fsync()
將內核緩衝區的數據寫入磁碟控制器的快取中。 - 磁碟控制器將快取中的數據寫入到磁碟的物理介質上。
在上面列出的5個步驟中,第1步到第3步數據都在記憶體中存放,一旦服務crash,那麼數據將永久性的丟失了。在第4步和第5步中,數據已經從記憶體轉移到了磁碟設備上,不過在第4步中數據是被寫入到了磁碟的控制器緩衝區(為了解決磁碟設備和記憶體設備訪問延遲的差異,通過緩衝區技術來提高單位時間內設備的吞吐量)中,所以一旦伺服器掉電宕機,在快取中的這部分數據也可能將會丟失(取決於物理存儲設備)。只有當數據經過第5步被寫入磁碟物理介質以後,數據才算真正地被保存了下來,不會因為伺服器掉電而丟失數據。
從上面的過程中我們可以發現,為了保證數據持久化過程的順利,我們只有成功地將上面第1到5步這五個步驟同步執行完成以後,數據才算安全的被持久化下來,其中任意一步出現問題,數據都可能存在丟失的風險。
當然,要想完全的執行完上面的五個步驟是很理想的情況,實際在實現持久化機制的時候,將會面臨一些現實的約束。首先,完成上述五個步驟涉及到Redis服務、作業系統以及底層存儲硬體的緊密配合,對於作業系統之上的Redis服務實現者來說,要實現持久化功能,只能通過調用作業系統提供的功能(系統調用System call)來完成對底層存儲硬體的訪問,所以實現者能控制的只有上述的第1-4這四步,至於最後一步則可能不受Redis服務的實現者控制,由各個硬體設備自己實現(至少不能保證能提供對應的內核驅動API供作業系統訪問)。所以在持久化這件事上,實現者能做的是保證第1-4步能順利完成。
其次,第3步和第4步都需要調用系統調用,調用系統調用會導致進程用戶態和內核態的切換,這個過程是有性能損失的。頻繁的調用系統調用將會降低服務的性能,而Redis作為一款高性能的記憶體資料庫,服務的性能也是需要重點考慮的一個指標。所以為了平衡好數據安全性和性能,在實現持久化機制的時候需要作出取捨。
Redis的實現者在實現持久化的時候,為了兼顧性能和數據安全性,引入了兩種持久化方案:
- RDB持久化
- AOF持久化
下面,我們將介紹這兩種持久化方案。
RDB持久化
RDB持久化是Redis引入的一種數據安全性相對弱的持久化方案,通過非同步將記憶體資料庫的快照寫入持久化文件來實現數據的持久化。由於生成快照是非同步進行的,所以快照不會實時反應記憶體中的資料庫情況,因此它是一種數據安全性較弱的數據持久化方案。不過由於創建快照過程是非同步進行的,在創建快照過程中基本不會對記憶體資料庫的操作產生影響,所以RDB持久化方案是一種注重性能,但是在數據安全性方面做出妥協的持久化方案。
下面,我們來看下RDB持久化的實現原理。
生成RDB快照
Redis提供了兩個命令:SAVE
和BGSAVE
來創建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
命令執行期間,BGSAVE
、SAVE
、BGREWRITEAOF
命令的執行將會受到限制。
首先,在BGSAVE
命令執行期間,SAVE
命令會被拒絕執行;其次,在BGSAVE
命令執行完成前,新的BGSAVE
命令也會被拒絕執行;最後,對於AOF重新命令BGREWRITEAOF
,在BGSAVE
命令執行期間,該命令將會被延後執行。同時,如果在BGSAVE
命令執行之前有BGREWRITEAOF
命令正在執行,則BGSAVE
命令也需要等到AOF重新命令完成以後才能被執行。這麼做是考慮到持久化是一個消耗IO資源的操作,雖然BGSAVE
和BGREWRITEAOF
兩個命令都是通過創建子進程來執行的,但是出於伺服器性能的考慮,這兩個命令不能同時執行。
定時生成快照
除了通過命令生成RDB快照以外,Redis也支援定時生成快照的功能。通過在Redis配置文件中設置快照生成配置,伺服器可以在運行過程中自動生成RDB快照。自動生成快照和BGSAVE
命令執行的效果類似,也是通過創建子進程的方式生成RDB快照文件。
用戶可以通過在配置文件中設置save
選項的值來控制自動生成快照的頻率。如果存在多個save
選項配置,則任意一個配置滿足條件都會觸發生成RDB快照。
save 900 1 save 300 10 save 60 10000
上述配置的三個條件,只要滿足下面任意一個條件,快照就會被創建:
- 伺服器在900秒內,記憶體資料庫發生了至少1次修改。
- 伺服器在300秒內,記憶體資料庫發生了至少10次修改。
- 伺服器在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部分組成的二進位文件。
- redis快照文件以
REDIS
字元串開頭,佔用5個位元組(C語言中一個char類型佔據1個位元組)。用於在載入的時候檢查文件是否是RDB快照文件。 db_version
的長度為4個位元組,是一個字元串表示的整數,記錄了RDB文件的版本號。databases
是一個變長的欄位,存放了Redis服務中的資料庫數據。如果Redis資料庫為空,則這個欄位的長度為0。EOF
是長度為1個位元組的常量,用於標識RDB文件正文內容的結束。check_sum
是一個長度為8個位元組的無符號整數,保存了前四部分的校驗和,用於在載入RDB文件的時候進行文件完整性檢查。
關於RDB文件格式的詳細細節可以參考《Redis設計與實現》一書,作者對RDB文件格式做了詳盡的介紹。
優缺點
優點
- 生成RDB快照文件的過程是非同步的,所以在持久化過程中對伺服器性能影響小。
- RDB文件存儲的是記憶體資料庫的快照,採用緊湊的二進位文件存儲。通過RDB文件進行資料庫恢復的時候速度快。
缺點
- 由於RDB文件是非同步進行備份的,所以存在數據安全性弱的弊端:當系統發生故障導致記憶體資料庫數據丟失的時候,從RDB文件中只能恢復創建RDB快照那一刻的數據,在最近一次創建RDB快照那一刻到伺服器宕機之間的數據將永久性的丟失了。數據恢復的完整程度依賴於RDB快照創建的頻率。
- 由於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持久化選項值:always
、everysec
、no
。分別對應不同的數據安全級別。
選項值 | 作用 |
---|---|
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重寫工作以後,會通過訊號機制通知父進程(伺服器進程),父進程接收到訊號以後會執行如下兩步操作:
- 伺服器進程會將AOF重寫緩衝區中的命令寫入重寫後的新AOF文件中。
- 伺服器會原子的將AOF文件修改為重寫後的AOF文件,完成新舊AOF文件的替換。
優缺點
優點
- 數據的安全性更高,當服務crash以後丟失的數據更少。
- AOF文件的可讀性更好。
- 通過append方式追加命令,訪問磁碟的效率高。
缺點
- 由於AOF持久化會在一定程度上進行磁碟同步處理操作,這個過程是阻塞的(雖然很短),所以對伺服器處理命令的性能會產生影響。
- AOF文件記錄的是寫命令,恢復的時候需要重放命令來得到記憶體資料庫的狀態,即使有AOF重寫機制,恢復速度上和RDB相比也會有差距。
從RDB持久化轉換成AOF持久化
在調整持久化方案的時候,Redis在不同版本有不同的操作方式。在Redis 2.2以後的版本中可以在不停機的情況下修改持久化方案,通過如下步驟進行操作:
- 創建最近的RDB文件的備份。
- 將備份保存在安全的位置。
- 發起如下命令:
- 客戶端執行
config set appendonly yes
啟用AOF持久化。 - 客戶端執行
config set save 「」
關閉RDB持久化。
- 客戶端執行
- 確認資料庫包含相同的keys。
- 確認write操作被正確追加到了AOF文件。
- 修改redis.conf文件,保證下次重啟的時候也是正確的持久化配置。
同時使用AOF持久化和RDB持久化
在AOF和RDB兩個持久化方案都啟動的情況下,除了RDB的快照命令SAVE
會阻塞進程導致其他命令都不能被執行之外,BGSAVE
和BGREWRITEAOF
命令都是在子進程中非同步執行的,不會影響到伺服器進程的正常執行。不過考慮到持久化操作會佔用磁碟IO資源,對機器的IO性能會產生影響,Redis對BGSAVE
和BGREWRITEAOF
命令的執行做了限制:同一時刻服務中只能執行一個命令。
同樣,在AOF和RDB這兩個持久化方案都啟用的情況下恢複數據的時候,由於AOF文件的數據安全性(完整性)更高,Redis在啟動的時候會優先採用AOF文件恢復的方式重建資料庫,只有當AOF功能關閉的情況下才會使用RDB進行數據重建。
總結
在本文中,我們介紹了Redis的兩種持久化方案,分析了兩種方案各自的實現原理和優缺點。