Redis是一個鍵值對數據庫服務器,服務器中通常包含著任意個非空數據庫,而每個非空數據庫中又可以包含任意個鍵值對,為了方便起見,我們將服務器中的非空數據庫以及它們的鍵值對統稱為數據庫狀態。
舉個例子,圖10-1展示了一個包含三個非空數據庫的Redis服務器,這三個數據庫以及數據庫中的鍵值對就是該服務器的數據庫狀態。
因為Redis是內存數據庫,它將自己的數據庫狀態儲存在內存里面,所以如果不想辦法將儲存在內存中的數據庫狀態保存到磁盤里面,那么一旦服務器進程退出,服務器中的數據庫狀態也會消失不見。
為了解決這個問題,Redis提供了RDB持久化功能,這個功能可以將Redis在內存中的數據庫狀態保存到磁盤里面,避免數據意外丟失
RDB持久化既可以手動執行,也可以根據服務器配置選項定期執行,該功能可以將某個時間點上的數據庫狀態保存到一個RDB文件中,如圖10-2所示。
RDB持久化功能所生成的RDB文件是一個經過壓縮的二進制文件,通過該文件可以還原生圖10-2 將數據庫狀態保存為RDB文件 圖10-3 用RDB文件來還原數據庫狀態
因為RDB文件是保存在硬盤里面的,所以即使Redis服務器進程退出,甚至運行Redis服務器的計算機停機,但只要RDB文件仍然存在,Redis服務器就可以用它來還原數據庫狀態。
本章首先介紹Redis服務器保存和載入RDB文件的方法,重點說明SAVE命令和BGSAVE命令的實現方式。
10.1 RDB 文件的創建與載入
有兩個Redis命令可以用于生成RDB文件,一個是SAVE,另一個是BGSAVE。
SAVE命令會阻塞Redis服務器進程,直到RDB文件創建完畢為止,在服務器進程阻塞期間,服務器不能處理任何命令請求:
redis> SAVE //等待直到RDB文件創建完畢
OK
和SAVE命令直接阻塞服務器進程的做法不同,BGSAVE命令會派生出一個子進程,然后由子進程負責創建RDB文件,服務器進程(父進程)繼續處理命令請求:
redis> BGSAVE //派生子進程,并由子進程創建RDB文件
Background saving started
創建RDB文件的實際工作由rdb.c/rdbSave函數完成,SAVE命令和BGSAVE命令會以不同的方式調用這個函數,通過以下偽代碼可以明顯地看出這兩個命令之間的區別:
def SAVE():
# 創建RDB文件
rdbSave()
def BGSAVE():
# 創建子進程
pid = fork()
if pid == 0:
# 子進程負責創建RDB文件
rdbSave()
# 完成之后向父進程發送信號
signal_parent()
elif pid > 0:
# 父進程繼續處理命令請求,并通過輪詢等待子進程的信號
handle_request_and_wait_signal()
else:
# 處理出錯情況
handle_fork_error()
和使用SAVE命令或者BGSAVE命令創建RDB文件不同,RDB文件的載入工作是在服務器啟動時自動執行的,所以Redis并沒有專門用于載入RDB文件的命令,只要Redis服務器在啟動時檢測到RDB文件存在,它就會自動載入RDB文件。
以下是Redis服務器啟動時打印的日志記錄,其中第二條日志DB loaded from disk:...就是服務器在成功載入RDB文件之后打印的:
$ redis-server
[7379] 30 Aug 21:07:01.270 # Server started, Redis version 2.9.11
[7379] 30 Aug 21:07:01.289 * DB loaded from disk: 0.018 seconds
[7379] ?30 Aug 21:07:01.289 * The server is now ready to accept connections on port 6379
另外值得一提的是,因為AOF文件的更新頻率通常比RDB文件的更新頻率高,所以:
■ 如果服務器開啟了AOF持久化功能,那么服務器會優先使用AOF文件來還原數據庫狀態。
■ 只有在AOF持久化功能處于關閉狀態時,服務器才會使用RDB文件來還原數據庫狀態
服務器判斷該用哪個文件來還原數據庫狀態的流程如圖10-4所示。
載入RDB文件的實際工作由rdb.c/rdbLoad函數完成,這個函數和rdbSave函數之間的關系可以用圖10-5表示。
前面提到過,當SAVE命令執行時,Redis服務器會被阻塞,所以當SAVE命令正在執行時,客戶端發送的所有命令請求都會被拒絕。只有在服務器執行完SAVE命令、重新開始接受命令請求之后,客戶端發送的命令才會被處理。
10.1.2 BGSAVE命令執行時的服務器狀態
因為BGSAVE命令的保存工作是由子進程執行的,所以在子進程創建RDB文件的過程中,Redis服務器仍然可以繼續處理客戶端的命令請求,但是,在BGSAVE命令執行期間,服務器處理SAVE、BGSAVE、BGREWRITEAOF三個命令的方式會和平時有所不同。
首先,在BGSAVE命令執行期間,客戶端發送的SAVE命令會被服務器拒絕,服務器禁止SAVE命令和BGSAVE命令同時執行是為了避免父進程(服務器進程)和子進程同時執行兩個rdbSave調用,防止產生競爭條件。
其次,在BGSAVE命令執行期間,客戶端發送的BGSAVE命令會被服務器拒絕,因為同時執行兩個BGSAVE命令也會產生競爭條件。
最后,BGREWRITEAOF和BGSAVE兩個命令不能同時執行:
■ 如果BGSAVE命令正在執行,那么客戶端發送的BGREWRITEAOF命令會被延遲到BGSAVE命令執行完畢之后執行。
■ 如果BGREWRITEAOF命令正在執行,那么客戶端發送的BGSAVE命令會被服務器拒絕。
因為BGREWRITEAOF和BGSAVE兩個命令的實際工作都由子進程執行,所以這兩個命令在操作方面并沒有什么沖突的地方,不能同時執行它們只是一個性能方面的考慮——并發出兩個子進程,并且這兩個子進程都同時執行大量的磁盤寫入操作,這怎么想都不會是一個好主意。
10.2 自動間隔性保存
在上一節,我們介紹了SAVE命令和BGSAVE的實現方法,并且說明了這兩個命令在實現方面的主要區別:SAVE命令由服務器進程執行保存工作,BGSAVE命令則由子進程執行保存工作,所以SAVE命令會阻塞服務器,而BGSAVE命令則不會。
因為BGSAVE命令可以在不阻塞服務器進程的情況下執行,所以Redis允許用戶通過設置服務器配置的save選項,讓服務器每隔一段時間自動執行一次BGSAVE命令。
用戶可以通過save選項設置多個保存條件,但只要其中任意一個條件被滿足,服務器就會執行BGSAVE命令。
舉個例子,如果我們向服務器提供以下配置:
save 900 1
save 300 10
save 60 10000
那么只要滿足以下三個條件中的任意一個,BGSAVE命令就會被執行:
■ 服務器在900秒之內,對數據庫進行了至少1次修改。
■ 服務器在300秒之內,對數據庫進行了至少10次修改。
■ 服務器在60秒之內,對數據庫進行了至少10000次修改。
舉個例子,以下是Redis服務器在60秒之內,對數據庫進行了至少10000次修改之后,服務器自動執行BGSAVE命令時打印出來的日志
[5085] 03 Sep 17:09:49.463 * 10000 changes in 60 seconds. Saving...
[5085] 03 Sep 17:09:49.463 * Background saving started by pid 5189
[5189] 03 Sep 17:09:49.522 * DB saved on disk
[5189] 03 Sep 17:09:49.522 * RDB: 0 MB of memory used by copy-on-write
[5085] 03 Sep 17:09:49.563 * Background saving terminated with success
10.2.1 設置保存條件
當Redis服務器啟動時,用戶可以通過指定配置文件或者傳入啟動參數的方式設置save選項,如果用戶沒有主動設置save選項,那么服務器會為save選項設置默認條件:
save 900 1
save 300 10
save 60 10000
接著,服務器程序會根據save選項所設置的保存條件,設置服務器狀態redisServer結構的saveparams屬性:
struct redisServer {
// ...
// 記錄了保存條件的數組
struct saveparam *saveparams;
// ...
};
saveparams屬性是一個數組,數組中的每個元素都是一個saveparam結構,每個saveparam結構都保存了一個save選項設置的保存條件:
struct saveparam {
// 秒數
time_t seconds;
// 修改數
int changes;
};
比如說,如果save選項的值為以下條件:
save 900 1
save 300 10
save 60 10000
那么服務器狀態中的saveparams數組將會是圖10-6所示的樣子。
10.2.2 dirty計數器和lastsave屬性
除了saveparams數組之外,服務器狀態還維持著一個dirty計數器,以及一個lastsave屬性:
■ dirty計數器記錄距離上一次成功執行SAVE命令或者BGSAVE命令之后,服務器對數據庫狀態(服務器中的所有數據庫)進行了多少次修改(包括寫入、刪除、更新等操作)。
■ lastsave屬性是一個UNIX時間戳,記錄了服務器上一次成功執行SAVE命令或者BGSAVE命令的時間。
struct redisServer {
// ...
// 修改計數器
long long dirty;
// 上一次執行保存的時間
time_t lastsave;
// ...
};
當服務器成功執行一個數據庫修改命令之后,程序就會對dirty計數器進行更新:命令修改了多少次數據庫,dirty計數器的值就增加多少。
例如,如果我們為一個字符串鍵設置值:
redis> SET message "hello"
OK
那么程序會將dirty計數器的值增加1。
圖10-7展示了服務器狀態中包含的dirty計數器和lastsave屬性,說明如下:
■ dirty計數器的值為123,表示服務器在上次保存之后,對數據庫狀態共進行了123次修改。
■ lastsave屬性則記錄了服務器上次執行保存操作的時間1378270800(2013年9月4日零時)
10.2.3 檢查保存條件是否滿足
Redis的服務器周期性操作函數serverCron默認每隔100毫秒就會執行一次,該函數用于對正在運行的服務器進行維護,它的其中一項工作就是檢查save選項所設置的保存條件是否已經滿足,如果滿足的話,就執行BGSAVE命令。
以下偽代碼展示了serverCron函數檢查保存條件的過程
def serverCron():
# ...
# 遍歷所有保存條件
for saveparam in server.saveparams:
# 計算距離上次執行保存操作有多少秒
save_interval = unixtime_now()?-?server.lastsave
# 如果數據庫狀態的修改次數超過條件所設置的次數
# 并且距離上次保存的時間超過條件所設置的時間
# 那么執行保存操作
if server.dirty >= saveparam.changes and \
save_interval > saveparam.seconds:
BGSAVE()
# ...
程序會遍歷并檢查saveparams數組中的所有保存條件,只要有任意一個條件被滿足,那么服務器就會執行BGSAVE命令。
舉個例子,如果Redis服務器的當前狀態如圖10-8所示。
那么當時間來到1378271101,也即是1378270800的301秒之后,服務器將自動執行一次BGSAVE命令,因為saveparams數組的第二個保存條件——300秒之內有至少10次修改——已經被滿足。
假設BGSAVE在執行5秒之后完成,那么圖10-8所示的服務器狀態將更新為圖10-9,其中dirty計數器已經被重置為0,而lastsave屬性也被更新為1378271106。
以上就是Redis服務器根據save選項所設置的保存條件,自動執行BGSAVE命令,進行間隔性數據保存的實現原理。
10.3 RDB 文件結構
在這一節,我們將對RDB文件本身進行介紹,并詳細說明文件各個部分的結構和意義
圖10-10展示了一個完整RDB文件所包含的各個部分。
RDB文件的最開頭是REDIS部分,這個部分的長度為5字節,保存著"REDIS"五個字符。通過這五個字符,程序可以在載入文件時,快速檢查所載入的文件是否RDB文件。
注意: 因為RDB文件保存的是二進制數據,而不是C字符串,為了簡便起見,我們用"REDIS"符號代表'R'、'E'、'D'、'I'、'S'五個字符,而不是帶'\0'結尾符號的C字符串'R'、'E'、'D'、'I'、'S'、'\0'
db_version長度為4字節,它的值是一個字符串表示的整數,這個整數記錄了RDB文件的版本號,比如"0006"就代表RDB文件的版本為第六版。
databases部分包含著零個或任意多個數據庫,以及各個數據庫中的鍵值對數據:
■ 如果服務器的數據庫狀態為空(所有數據庫都是空的),那么這個部分也為空,長度為0字節。
■ 如果服務器的數據庫狀態為非空(有至少一個數據庫非空),那么這個部分也為非空,根據數據庫所保存鍵值對的數量、類型和內容不同,這個部分的長度也會有所不同。
EOF常量的長度為1字節,這個常量標志著RDB文件正文內容的結束,當讀入程序遇到這個值的時候,它知道所有數據庫的所有鍵值對都已經載入完畢了。
check_sum是一個8字節長的無符號整數,保存著一個校驗和,這個校驗和是程序通過對REDIS、db_version、databases、EOF四個部分的內容進行計算得出的。服務器在載入RDB文件時,會將載入數據所計算出的校驗和與check_sum所記錄的校驗和進行對比,以此來檢查RDB文件是否有出錯或者損壞的情況出現。
作為例子,圖10-11展示了一個databases部分為空的RDB文件:文件開頭的"REDIS"表示這是一個RDB文件,之后的"0006"表示這是第六版的RDB文件,因為databases為空,所以版本號之后直接跟著EOF常量,最后的6265312314761917404是文件的校驗和。
10.3.1 databases 部分
一個RDB文件的databases部分可以保存任意多個非空數據庫。
例如,如果服務器的0號數據庫和3號數據庫非空,那么服務器將創建一個如圖10-12所示的RDB文件,圖中的database 0代表0號數據庫中的所有鍵值對數據,而database 3則代表3號數據庫中的所有鍵值對數據
每個非空數據庫在RDB文件中都可以保存為SELECTDB、db_number、key_value_pairs三個部分,如圖10-13所示。
SELECTDB常量的長度為1字節,當讀入程序遇到這個值的時候,它知道接下來要讀入的將是一個數據庫號碼。
db_number保存著一個數據庫號碼,根據號碼的大小不同,這個部分的長度可以是1字節、2字節或者5字節(變長? 怎么確定讀幾個字節呢??)。當程序讀入db_number部分之后,服務器會調用SELECT命令,根據讀入的數據庫號碼進行數據庫切換,使得之后讀入的鍵值對可以載入到正確的數據庫中。
key_value_pairs部分保存了數據庫中的所有鍵值對數據,如果鍵值對帶有過期時間,那么過期時間也會和鍵值對保存在一起。根據鍵值對的數量、類型、內容以及是否有過期時間等條件的不同,key_value_pairs部分的長度也會有所不同。
作為例子,圖10-14展示了RDB文件中,0號數據庫的結構。
另外,圖10-15則展示了一個完整的RDB文件,文件中包含了0號數據庫和3號數據庫。
10.3.2 key_value_pairs 部分
RDB文件中的每個key_value_pairs部分都保存了一個或以上數量的鍵值對,如果鍵值對帶有過期時間的話,那么鍵值對的過期時間也會被保存在內。
不帶過期時間的鍵值對在RDB文件中由TYPE、key、value三部分組成,如圖10-16所示。
TYPE記錄了value的類型,長度為1字節,值可以是以下常量的其中一個:
■ REDIS_RDB_TYPE_STRING
■ REDIS_RDB_TYPE_LIST
■ REDIS_RDB_TYPE_SET
■ REDIS_RDB_TYPE_ZSET
■ REDIS_RDB_TYPE_HASH
■ REDIS_RDB_TYPE_LIST_ZIPLIST
■ REDIS_RDB_TYPE_SET_INTSET
■ REDIS_RDB_TYPE_ZSET_ZIPLIST
■ REDIS_RDB_TYPE_HASH_ZIPLIST
以上列出的每個TYPE常量都代表了一種對象類型或者底層編碼,當服務器讀入RDB文件中的鍵值對數據時,程序會根據TYPE的值來決定如何讀入和解釋value的數據。key和value分別保存了鍵值對的鍵對象和值對象
■ 其中key總是一個字符串對象,它的編碼方式和REDIS_RDB_TYPE_STRING類型的value一樣。根據內容長度的不同,key的長度也會有所不同。
■ 根據TYPE類型的不同,以及保存內容長度的不同,保存value的結構和長度也會有所不同,本節稍后會詳細說明每種TYPE類型的value結構保存方式。
帶有過期時間的鍵值對在RDB文件中的結構如圖10-17所示
帶有過期時間的鍵值對中的TYPE、key、value三個部分的意義,和前面介紹的不帶過期時間的鍵值對的TYPE、key、value三個部分的意義完全相同,至于新增的EXPIRETIME_MS和ms,它們的意義如下:
■ EXPIRETIME_MS常量的長度為1字節,它告知讀入程序,接下來要讀入的將是一個以毫秒為單位的過期時間。
■ ms是一個8字節長的帶符號整數,記錄著一個以毫秒為單位的UNIX時間戳,這個時間戳就是鍵值對的過期時間。
作為例子,圖10-18展示了一個沒有過期時間的字符串鍵值對。
圖10-19展示了一個帶有過期時間的集合鍵值對,其中鍵的過期時間為1388556000000(2014年1月1日零時)。
10.3.3 value 的編碼
RDB文件中的每個value部分都保存了一個值對象,每個值對象的類型都由與之對應的TYPE記錄,根據類型的不同,value部分的結構、長度也會有所不同。
1.字符串對象
如果TYPE的值為REDIS_RDB_TYPE_STRING,那么value保存的就是一個字符串對象,字符串對象的編碼可以是REDIS_ENCODING_INT或者REDIS_ENCODING_RAW。
如果字符串對象的編碼為REDIS_ENCODING_INT,那么說明對象中保存的是長度不超過32位的整數,這種編碼的對象將以圖10-20所示的結構保存。
其中,ENCODING的值可以是REDIS_RDB_ENC_INT8、REDIS_RDB_ENC_INT16或者REDIS_RDB_ENC_INT32三個常量的其中一個,它們分別代表RDB文件使用8位(bit)、16位或者32位來保存整數值integer。
舉個例子,如果字符串對象中保存的是可以用8位來保存的整數123,那么這個對象在RDB文件中保存的結構將如圖10-21所示。
如果字符串對象的編碼為REDIS_ENCODING_RAW,那么說明對象所保存的是一個字符串值,根據字符串長度的不同,有壓縮和不壓縮兩種方法來保存這個字符串:
■ 如果字符串的長度小于等于20字節,那么這個字符串會直接被原樣保存。
■ 如果字符串的長度大于20字節,那么這個字符串會被壓縮之后再保存。
以上兩個條件是在假設服務器打開了RDB 文件壓縮功能的情況下進行的,如果服務器關閉了RDB 文件壓縮功能,那么RDB 程序總以無壓縮的方式保存字符串值。
具體信息可以參考redis.conf文件中關于rdbcompression選項的說明。
對于沒有被壓縮的字符串,RDB程序會以圖10-22所示的結構來保存該字符串。
其中,string部分保存了字符串值本身,而len保存了字符串值的長度。對于壓縮后的字符串,RDB程序會以圖10-23所示的結構來保存該字符串。
其中,REDIS_RDB_ENC_LZF常量標志著字符串已經被LZF算法(http://liblzf.plan9.de)壓縮過了,讀入程序在碰到這個常量時,會根據之后的compressed_len、origin_len和compressed_string三部分,對字符串進行解壓縮:其中compressed_len記錄的是字符串被壓縮之后的長度,而origin_len記錄的是字符串原來的長度,compressed_string記錄的則是被壓縮之后的字符串。
圖10-24展示了一個保存無壓縮字符串的例子,其中字符串的長度為5,字符串的值為"hello"。
圖10-25展示了一個壓縮后的字符串示例,從圖中可以看出,字符串原本的長度為21,壓縮之后的長度為6,壓縮之后的字符串內容為" aa ",其中 代表的是無法用字符串形式打印出來的字節。
2.列表對象
如果TYPE的值為REDIS_RDB_TYPE_LIST,那么value保存的就是一個REDIS_ENCODING_LINKEDLIST編碼的列表對象,RDB文件保存這種對象的結構如圖10-26所示。
list_length記錄了列表的長度,它記錄列表保存了多少個項(item),讀入程序可以通過這個長度知道自己應該讀入多少個列表項。
圖中以item開頭的部分代表列表的項,因為每個列表項都是一個字符串對象,所以程序會以處理字符串對象的方式來保存和讀入列表項。
作為示例,圖10-27展示了一個包含三個元素的列表。
結構中的第一個數字3是列表的長度,之后跟著的分別是第一個列表項、第二個列表項和第三個列表項,其中:
■ 第一個列表項的長度為5,內容為字符串"hello"。
■ 第二個列表項的長度也為5,內容為字符串"world"。
■ 第三個列表項的長度為1,內容為字符串"!"。
3.集合對象
如果TYPE的值為REDIS_RDB_TYPE_SET,那么value保存的就是一個REDIS_ENCODING_HT編碼的集合對象,RDB文件保存這種對象的結構如圖10-28所示。
其中,set_size是集合的大小,它記錄集合保存了多少個元素,讀入程序可以通過這個大小知道自己應該讀入多少個集合元素。
圖中以elem開頭的部分代表集合的元素,因為每個集合元素都是一個字符串對象,所以程序會以處理字符串對象的方式來保存和讀入集合元素。
作為示例,圖10-29展示了一個包含四個元素的集合
結構中的第一個數字4記錄了集合的大小,之后跟著的是集合的四個元素:
■ 第一個元素的長度為5,值為"apple"。
■ 第二個元素的長度為6,值為"banana"。
■ 第三個元素的長度為3,值為"cat"。
■ 第四個元素的長度為3,值為"dog"。
4.哈希表對象
如果TYPE的值為REDIS_RDB_TYPE_HASH,那么value保存的就是一個REDIS_ENCODING_HT編碼的集合對象,RDB文件保存這種對象的結構如圖10-30所示:
■ hash_size記錄了哈希表的大小,也即是這個哈希表保存了多少鍵值對,讀入程序可以通過這個大小知道自己應該讀入多少個鍵值對。
■ 以key_value_pair開頭的部分代表哈希表中的鍵值對,鍵值對的鍵和值都是字符串對象,所以程序會以處理字符串對象的方式來保存和讀入鍵值對。
結構中的每個鍵值對都以鍵緊挨著值的方式排列在一起,如圖10-31所示。
因此,從更詳細的角度看,圖10-30所展示的結構可以進一步修改為圖10-32。
作為示例,圖10-33展示了一個包含兩個鍵值對的哈希表。
在這個示例結構中,第一個數字2記錄了哈希表的鍵值對數量,之后跟著的是兩個鍵值對:
■ 第一個鍵值對的鍵是長度為1的字符串"a",值是長度為5的字符串"apple"。
■ 第二個鍵值對的鍵是長度為1的字符串"b",值是長度為6的字符串"banana"。
10.4 分析RDB文件
通過上一節對RDB文件的介紹,我們現在應該對RDB文件中的各種內容和結構有一定的了解了,是時候拋開單純的圖片示例,開始分析和觀察一下實際的RDB文件了
我們使用od命令來分析Redis服務器產生的RDB文件,該命令可以用給定的格式轉存(dump)并打印輸入文件。比如說,給定-c參數可以以ASCII編碼的方式打印輸入文件,給定-x參數可以以十六進制的方式打印輸入文件,諸如此類,具體的信息可以參考od命令的文檔。
讓我們首先從最簡單的情況開始,執行以下命令,創建一個數據庫狀態為空的RDB文件:
redis> FLUSHALL
OK
redis> SAVE
OK
然后調用od命令,打印RDB文件:
$ od -c dump.rdb
0000000 R E D I S 0 0 0 6 377 334 263 C 360 Z 334
0000020 362 V
0000022
根據之前學習的RDB文件結構知識,當一個RDB文件沒有包含任何數據庫數據時,這個RDB文件將由以下四個部分組成:
■ 五個字節的"REDIS"字符串。
■ 四個字節的版本號(db_version)。
■ 一個字節的EOF常量。
■ 八個字節的校驗和(check_sum)
從od命令的輸出中可以看到,最開頭的是"REDIS"字符串,之后的0006是版本號,再之后的一個字節377代表EOF常量,最后的334 263 C 360 Z 334 362 V八個字節則代表RDB文件的校驗和
包含字符串鍵的RDB 文件
這次我們來分析一個帶有單個字符串鍵的數據庫:
redis> FLUSHALL
OK
redis> SET MSG "HELLO"
OK
redis> SAVE
OK
再次執行od命令:
$ od -c dump.rdb
0000000 R E D I S 0 0 0 6 376 \0 \0 003 M S G
0000020 005 H E L L O 377 207 z = 304 f T L 343
0000037
根據之前學習的數據庫結構知識,當一個數據庫被保存到RDB文件時,這個數據庫將由以下三部分組成:
■ 一個一字節長的特殊值SELECTDB。
■ 一個長度可能為一字節、兩字節或者五字節的數據庫號碼(db_number)。
■ 一個或以上數量的鍵值對(key_value_pairs)。
觀察od命令打印的輸出,RDB文件的最開始仍然是REDIS和版本號0006,之后出現的376代表SELECTDB常量,再之后的\0代表整數0,表示被保存的數據庫為0號數據庫。
在數據庫號碼之后,直到代表EOF常量的377為止,RDB文件包含有以下內容:
\0 003 M S G 005 H E L L O
根據之前學習的鍵值對結構知識,在RDB文件中,沒有過期時間的鍵值對由類型(TYPE)、鍵(key)、值(value)三部分組成:其中類型的長度為一字節,鍵和值都是字符串對象,并且字符串在未被壓縮前,都是以字符串長度為前綴,后跟字符串內容本身的方式來儲存的。
根據這些特征,我們可以確定\0就是字符串類型的TYPE值REDIS_RDB_TYPE_STRING(這個常量的實際值為整數0),之后的003是鍵MSG的長度值,再之后的005則是值HELLO的長度。
10.4.3 包含帶有過期時間的字符串鍵的RDB 文件
redis> FLUSHALL
OK
redis> SETEX MSG 10086 "HELLO"
OK
redis> SAVE
OK
打印RDB文件:
$ od -c dump.rdb
0000000 R E D I S 0 0 0 6 376 \0 374 \ 2 365 336
0000020 @ 001 \0 \0 \0 003 M S G 005 H E L L O 377
0000040 212 231 x 247 252 } 021 306
0000050
根據之前學習的鍵值對結構知識,一個帶有過期時間的鍵值對將由以下部分組成:
■ 一個一字節長的EXPIRETIME_MS 特殊值。
■ 一個八字節長的過期時間(ms)。
■ 一個一字節長的類型(TYPE)。
■ 一個鍵(key)和一個值(value)。
根據這些特征,可以得出RDB文件各個部分的意義:
■ REDIS0006:RDB 文件標志和版本號。
■ 376 \0:切換到0號數據庫。
■ 374:代表特殊值EXPIRETIME_MS。
■ \ 2 365 336 @ 001 \0 \0:代表八字節長的過期時間。
■ \0 003 M S G :\0 表示這是一個字符串鍵,003是鍵的長度,MSG是鍵。
■ 005 H E L L O:005是值的長度,HELLO是值。
■ 377 :代表EOF常量。
■ 212 231 x 247 252 } 021 306:代表八字節長的校驗和
小結
■ RDB 文件用于保存和還原Redis服務器所有數據庫中的所有鍵值對數據。
■ SAVE命令由服務器進程直接執行保存操作,所以該命令會阻塞服務器。
■ BGSAVE令由子進程執行保存操作,所以該命令不會阻塞服務器。
■ 服務器狀態中會保存所有用save選項設置的保存條件,當任意一個保存條件被滿足時,服務器會自動執行BGSAVE命令
■ RDB文件是一個經過壓縮的二進制文件,由多個部分組成。
■ 對于不同類型的鍵值對,RDB 文件會使用不同的方式來保存它們。