本篇部落格是Redis系列的第3篇,主要講解下Redis的2種持久化機制:RDB和AOF。
本系列的前2篇可以點選以下連結檢視:
1. 為什麼需要持久化?
因為Redis是記憶體資料庫,它將自己的資料儲存在記憶體裡面,一旦Redis伺服器程式退出或者執行Redis伺服器的計算機停機,Redis伺服器中的資料就會丟失。
為了避免資料丟失,所以Redis提供了持久化機制,將儲存在記憶體中的資料儲存到磁碟中,用於在Redis伺服器程式退出或者執行Redis伺服器的計算機停機導致資料丟失時,快速的恢復之前Redis儲存在記憶體中的資料。
Redis提供了2種持久化方式,分別為:
- RDB持久化
- AOF持久化
接下來,我們一一詳解。
2. RDB持久化
RDB持久化是將某個時間點上Redis中的資料儲存到一個RDB檔案中,如下所示:
基於RDB持久化的上述性質,所以RDB持久化也叫做快照持久化。
該檔案是一個經過壓縮的二進位制檔案,通過該檔案可以還原生成RDB檔案時Redis中的資料,如下所示:
2.1 建立RDB檔案
Redis提供了2個命令來建立RDB檔案,一個是SAVE,另一個是BGSAVE。
SAVE命令會阻塞Redis伺服器程式,直到RDB檔案建立完畢為止,在伺服器程式阻塞期間,伺服器不能處理任何命令請求,如下所示:
BGSAVE命令會派生出一個子程式,然後由子程式負責建立RDB檔案,伺服器程式(父程式)繼續處理命令請求,如下所示:
以上描述也是這2個命令的區別,這裡是重點,面試經常會問到。
因為BGSAVE命令可以在不阻塞伺服器程式的情況下執行,所以推薦使用BGSAVE命令。
我們可以手動執行該命令,如上面截圖所示,但還是推薦設定下Redis伺服器配置檔案的save選項,讓伺服器每隔一段時間自動執行一次BGSAVE命令。
我們可以通過save選項設定多個儲存條件,只要其中任意一個條件被滿足,伺服器就會執行BGSAVE命令。
save選項設定的預設條件如下所示:
save 900 1
save 300 10
save 60 10000
預設的配置條件表示,只要滿足以下3個條件中的任意1個,BGSAVE命令就會被執行:
- 伺服器在900s(即15分鐘)之內,對資料庫進行了至少1次修改
- 伺服器在300s(即5分鐘)之內,對資料庫進行了至少10次修改
- 伺服器在60s(即1分鐘)之內,對資料庫進行了至少10000次修改
當滿足條件執行BGSAVE命令時,輸出日誌如下圖所示:
生成的RDB檔案會根據Redis配置檔案中的名稱和路徑來儲存,相關的2個配置如下所示:
最終生成的RDB檔案如下所示(截圖為本機Windows環境,Linux環境下路徑會稍有不同):
2.2 載入RDB檔案
首先,我們要明確的是,載入RDB檔案的目的是為了在Redis伺服器程式重新啟動之後還原之前儲存在Redis中的資料。
然後,Redis載入RDB檔案並沒有專門的命令,而是在Redis伺服器啟動時自動執行的。
而且,Redis伺服器啟動時是否會載入RDB檔案還取決於伺服器是否啟用了AOF持久化功能,具體判斷邏輯為:
- 只有在AOF持久化功能處於關閉狀態時,伺服器才會使用RDB檔案來還原資料。
- 如果伺服器開啟了AOF持久化功能,那麼伺服器會優先使用AOF檔案來還原資料。
以上判斷邏輯如下圖所示:
預設情況下,Redis伺服器的AOF持久化功能是關閉的,所以Redis伺服器在啟動時會載入RDB檔案,
啟動日誌如下所示:
2.3 伺服器狀態
建立和載入RDB檔案,可能存在的伺服器狀態有以下3種:
- 當執行SAVE命令時,Redis伺服器會被阻塞,此時客戶端傳送的所有命令請求都會被阻塞,只有在伺服器執行完SAVE命令,重新開始接受命令請求之後,客戶端傳送的命令請求才會被處理。
- 當執行BGSAVE命令時,Redis伺服器不會被阻塞,Redis伺服器仍然可以繼續處理客戶端傳送的命令請求。
- 伺服器在載入RDB檔案期間,會一直處於阻塞狀態,直到RDB檔案載入成功。
3. AOF持久化
AOF持久化是通過儲存Redis伺服器所執行的寫命令來記錄資料庫資料的,如下圖所示:
預設情況下,AOF持久化功能是關閉的,如果想要開啟,可以修改下圖所示的配置:
舉個例子,假設Redis中還沒有儲存任何資料,我們執行了如下所示的命令:
然後我們會發現Redis伺服器生成了1個名為appendonly.aof的檔案,開啟該檔案,我們可以看到上面執行的3個寫命令都儲存在該檔案中:
3.1 AOF持久化的實現
當AOF持久化功能處於開啟狀態時,Redis伺服器在執行完一個寫命令之後,會以協議格式(如上面截圖中AOF檔案裡儲存寫命令的格式)將被執行的寫命令追加到伺服器狀態的AOF緩衝區的末尾,然後Redis伺服器會根據配置檔案中appendfsync選項的值來決定何時將AOF緩衝區中的內容寫入和同步到AOF檔案裡面。
appendfsync選項有以下3個值:
-
always
從安全性來說,always是最安全的(丟失資料最少),因為即使出現故障停機,資料庫也只會丟失一個事件迴圈中所產生的命令資料。
從效率來說,always的效率最慢,因為伺服器在每個事件迴圈都要將AOF緩衝區中的所有內容寫入到AOF檔案,並且同步AOF檔案。
-
everysec
從安全性來說,everysec模式下,即使出現故障停機,資料庫只會丟失一秒鐘的命令資料。
從效率來說,everysec模式足夠快,因為伺服器在每個事件迴圈都要將AOF緩衝區中的所有內容寫入到AOF檔案,並且每隔一秒就要在子執行緒中對AOF檔案進行同步。
-
no
從安全性來說,no模式下,如果出現故障停機,資料庫會丟失上次同步AOF檔案之後的所有寫命令資料,具有不確定性,因為伺服器在每個事件迴圈都要將AOF緩衝區中的所有內容寫入到AOF檔案,至於何時對AOF檔案進行同步,則由作業系統控制。
從效率來說,no模式和everysec模式的效率差不多。
appendfsync選項的預設值是everysec,也推薦使用這個值,因為既保證了效率又保證了安全性。
3.2 載入AOF檔案
因為AOF檔案包含了重建資料庫所需的所有寫命令,所以Redis伺服器只要讀入並重新執行一遍AOF檔案裡面儲存的寫命令,就可以還原Redis伺服器關閉之前的資料。
Redis讀取AOF檔案並還原資料庫的詳細步驟如下:
-
建立一個不帶網路連線的偽客戶端
因為Redis的命令只能在客戶端上下文中執行,而載入AOF檔案時所使用的命令直接來源於AOF檔案而不是網路連線,所以伺服器使用了一個沒有網路連線的偽客戶端來執行AOF檔案儲存的寫命令。
偽客戶端執行命令的效果和帶網路連線的客戶端執行命令的效果完全一樣。
-
從AOF檔案中分析並讀取出一條寫命令。
-
使用偽客戶端執行被讀取出的寫命令。
-
一直執行步驟2和步驟3,直到AOF檔案中的所有寫命令都被執行完畢。
以上步驟如下圖所示:
如果Redis伺服器開啟了AOF持久化功能,那麼Redis伺服器在啟動時會載入AOF檔案,
啟動日誌如下所示:
3.3 AOF重寫
因為AOF持久化是通過儲存被執行的寫命令來記錄資料庫資料的,所以隨著Redis伺服器執行時間的增加,AOF檔案中的內容會越來越多,檔案的體積會越來越大,如果不做控制,會有以下2點壞處:
- 過多的佔用伺服器磁碟空間,可能會對Redis伺服器甚至整個宿主計算機造成影響。
- AOF檔案的體積越大,使用AOF檔案來進行資料庫還原所需的時間就越多。
舉個例子,在客戶端執行如下命令:
為了記錄這個list鍵的狀態,AOF檔案就需要儲存上面執行的6條命令。
為了解決AO檔案體積越來越大的問題,Redis提供了AOF檔案重寫功能,即Redis伺服器會建立一個新的AOF檔案來替代現有的AOF檔案,新舊兩個AOF檔案所儲存的資料庫資料相同,但新AOF檔案不會包含任何浪費空間的冗餘命令,所以新AOF檔案的體積通常會比舊AOF檔案的體積要小很多。
3.3.1 AOF重寫的實現原理
AOF檔案重寫並不需要對現有的AOF檔案進行任何讀取、分析或者寫入操作,而是通過讀取伺服器當前的資料庫資料來實現的。
仍然以上面的list鍵為例,舊的AOF檔案儲存了6條命令來記錄list鍵的狀態,但list鍵的結果是“C” "D" "E" "F" "G"這樣的資料,所以AOF檔案重寫時,可以用一條RPUSH list “C” "D" "E" "F" "G"
命令來代替之前的六條命令,這樣就可以將儲存list鍵所需的命令從六條減少為一條了。
按照上面的原理,如果Redis伺服器儲存的鍵值對足夠多,AOF檔案重寫生成的新AOF檔案就會減少很多很多的冗餘命令,進而大大減小了AOF檔案的體積。
綜上所述,AOF檔案重寫功能的實現原理為:
首先從資料庫中讀取鍵現在的值,然後用一條命令去記錄鍵值對,代替之前記錄這個鍵值對的多條命令。
3.3.2 AOF後臺重寫
因為AOF檔案重寫會進行大量的檔案寫入操作,所以執行這個操作的執行緒將被長時間阻塞。
因為Redis伺服器使用單個執行緒來處理命令請求,所以如果由伺服器程式直接執行這個操作,那麼在重寫AOF檔案期間,伺服器將無法處理客戶端傳送過來的命令請求。
為了避免上述問題,Redis將AOF檔案重寫功能放到子程式裡執行,這樣做有以下2個好處:
- 子程式進行AOF檔案重寫期間,伺服器程式(父程式)可以繼續處理命令請求。
- 子程式帶有伺服器程式的資料副本,使用子程式而不是執行緒,可以在避免使用鎖的情況下,保證資料的安全性。
AOF後臺重寫的步驟如下所示:
-
伺服器程式建立子程式,子程式開始AOF檔案重寫
-
從建立子程式開始,伺服器程式執行的所有寫命令不僅要寫入AOF緩衝區,還要寫入AOF重寫緩衝區
寫入AOF緩衝區的目的是為了同步到原有的AOF檔案。
寫入AOF重寫緩衝區的目的是因為子程式在進行AOF檔案重寫期間,伺服器程式還在繼續處理命令請求,
而新的命令可能會對現有的資料庫進行修改,從而使得伺服器當前的資料庫資料和重寫後的AOF檔案所
儲存的資料庫資料不一致。
-
子程式完成AOF重寫工作,向父程式傳送一個訊號,父程式在接收到該訊號後,會執行以下操作:
1.將AOF重寫緩衝區中的所有內容寫入到新AOF檔案中,這樣就保證了新AOF檔案所儲存的資料庫資料和伺服器當前的資料庫資料是一致的。
2.對新的AOF檔案進行改名,原子地覆蓋現有的AOF檔案,完成新舊兩個AOF檔案的替換。
Redis提供了BGREWRITEAOF
命令來執行以上步驟,如下圖所示:
執行完成後,開啟appendonly.aof檔案,發現儲存list鍵的命令從六條變為了一條:
除了手動執行BGREWRITEAOF
命令外,Redis還提供了2個配置項用來自動執行BGREWRITEAOF
命令:
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
該配置表示,當AOF檔案的體積大於64MB,並且AOF檔案的體積比上一次重寫之後的體積大了至少一倍(100%),Redis將自動執行BGREWRITEAOF
命令。
4. RDB持久化、AOF持久化的區別
通過上面的講解,我們會發現Redis提供的2種持久化方法是有區別的,可以總結為以下4點:
- 實現方式
- 檔案體積
- 安全性
- 優先順序
接下來一一講解。
4.1 實現方式
RDB持久化是通過將某個時間點Redis伺服器儲存的資料儲存到RDB檔案中來實現持久化的。
AOF持久化是通過將Redis伺服器執行的所有寫命令儲存到AOF檔案中來實現持久化的。
4.2 檔案體積
由上述實現方式可知,RDB持久化記錄的是結果,AOF持久化記錄的是過程,所以AOF持久化生成的AOF檔案會有體積越來越大的問題,Redis提供了AOF重寫功能來減小AOF檔案體積。
4.3 安全性
AOF持久化的安全性要比RDB持久化的安全性高,即如果發生機器故障,AOF持久化要比RDB持久化丟失的資料要少。
因為RDB持久化會丟失上次RDB持久化後寫入的資料,而AOF持久化最多丟失1s之內寫入的資料(使用預設everysec配置的話)。
4.4 優先順序
由於上述的安全性問題,如果Redis伺服器開啟了AOF持久化功能,Redis伺服器在啟動時會使用AOF檔案來還原資料,如果Redis伺服器沒有開啟AOF持久化功能,Redis伺服器在啟動時會使用RDB檔案來還原資料,所以AOF檔案的優先順序比RDB檔案的優先順序高。
5. 原始碼及參考
Josiah L. Carlson 《Reids實戰》
黃健巨集 《Redis設計與實現》