在本章之前的內(nèi)容中, 我們介紹了 Redis 服務(wù)器保存和載入 RDB 文件的方法, 在這一節(jié), 我們將對(duì) RDB 文件本身進(jìn)行介紹, 并詳細(xì)說(shuō)明文件各個(gè)部分的結(jié)構(gòu)和意義。
圖 IMAGE_RDB_STRUCT_OVERVIEW 展示了一個(gè)完整 RDB 文件所包含的各個(gè)部分。
注意
為了方便區(qū)分變量、數(shù)據(jù)、常量, 圖 IMAGE_RDB_STRUCT_OVERVIEW 中用全大寫單詞標(biāo)示常量, 用全小寫單詞標(biāo)示變量和數(shù)據(jù)。
本章展示的所有 RDB 文件結(jié)構(gòu)圖都遵循這一規(guī)則。
RDB 文件的最開(kāi)頭是 REDIS
部分, 這個(gè)部分的長(zhǎng)度為 5
字節(jié), 保存著 "REDIS"
五個(gè)字符。 通過(guò)這五個(gè)字符, 程序可以在載入文件時(shí), 快速檢查所載入的文件是否 RDB 文件。
注意
因?yàn)?RDB 文件保存的是二進(jìn)制數(shù)據(jù), 而不是 C 字符串, 為了簡(jiǎn)便起見(jiàn), 我們用 "REDIS"
符號(hào)代表 'R'
、 'E'
、 'D'
、 'I'
、 'S'
五個(gè)字符, 而不是帶 '\0'
結(jié)尾符號(hào)的 C 字符串 'R'
、 'E'
、 'D'
、 'I'
、 'S'
、 '\0'
。
本章介紹的所有內(nèi)容,以及展示的所有 RDB 文件結(jié)構(gòu)圖都遵循這一規(guī)則。
db_version
長(zhǎng)度為 4
字節(jié), 它的值是一個(gè)字符串表示的整數(shù), 這個(gè)整數(shù)記錄了 RDB 文件的版本號(hào), 比如 "0006"
就代表 RDB 文件的版本為第六版。 本章只介紹第六版 RDB 文件的結(jié)構(gòu)。
databases
部分包含著零個(gè)或任意多個(gè)數(shù)據(jù)庫(kù), 以及各個(gè)數(shù)據(jù)庫(kù)中的鍵值對(duì)數(shù)據(jù):
0
字節(jié)。EOF
常量的長(zhǎng)度為 1
字節(jié), 這個(gè)常量標(biāo)志著 RDB 文件正文內(nèi)容的結(jié)束, 當(dāng)讀入程序遇到這個(gè)值的時(shí)候, 它知道所有數(shù)據(jù)庫(kù)的所有鍵值對(duì)都已經(jīng)載入完畢了。
check_sum
是一個(gè) 8
字節(jié)長(zhǎng)的無(wú)符號(hào)整數(shù), 保存著一個(gè)校驗(yàn)和, 這個(gè)校驗(yàn)和是程序通過(guò)對(duì) REDIS
、 db_version
、 databases
、 EOF
四個(gè)部分的內(nèi)容進(jìn)行計(jì)算得出的。 服務(wù)器在載入 RDB 文件時(shí), 會(huì)將載入數(shù)據(jù)所計(jì)算出的校驗(yàn)和與 check_sum
所記錄的校驗(yàn)和進(jìn)行對(duì)比, 以此來(lái)檢查 RDB 文件是否有出錯(cuò)或者損壞的情況出現(xiàn)。
作為例子, 圖 IMAGE_RDB_WITH_EMPTY_DATABASE 展示了一個(gè) databases
部分為空的 RDB 文件: 文件開(kāi)頭的 "REDIS"
表示這是一個(gè) RDB 文件, 之后的 "0006"
表示這是第六版的 RDB 文件, 因?yàn)?nbsp;databases
為空, 所以版本號(hào)之后直接跟著 EOF
常量, 最后的 6265312314761917404
是文件的校驗(yàn)和。
一個(gè) RDB 文件的 databases
部分可以保存任意多個(gè)非空數(shù)據(jù)庫(kù)。
比如說(shuō), 如果服務(wù)器的 0
號(hào)數(shù)據(jù)庫(kù)和 3
號(hào)數(shù)據(jù)庫(kù)非空, 那么服務(wù)器將創(chuàng)建一個(gè)如圖 IMAGE_RDB_WITH_TWO_DB 所示的 RDB 文件, 圖中的database 0
代表 0
號(hào)數(shù)據(jù)庫(kù)中的所有鍵值對(duì)數(shù)據(jù), 而 database 3
則代表 3
號(hào)數(shù)據(jù)庫(kù)中的所有鍵值對(duì)數(shù)據(jù)。
每個(gè)非空數(shù)據(jù)庫(kù)在 RDB 文件中都可以保存為 SELECTDB
、 db_number
、 key_value_pairs
三個(gè)部分, 如圖 IMAGE_DATABASE_STRUCT_OF_RDB 所示。
SELECTDB
常量的長(zhǎng)度為 1
字節(jié), 當(dāng)讀入程序遇到這個(gè)值的時(shí)候, 它知道接下來(lái)要讀入的將是一個(gè)數(shù)據(jù)庫(kù)號(hào)碼。
db_number
保存著一個(gè)數(shù)據(jù)庫(kù)號(hào)碼, 根據(jù)號(hào)碼的大小不同, 這個(gè)部分的長(zhǎng)度可以是 1
字節(jié)、 2
字節(jié)或者 5
字節(jié)。 當(dāng)程序讀入 db_number
部分之后, 服務(wù)器會(huì)調(diào)用 SELECT 命令, 根據(jù)讀入的數(shù)據(jù)庫(kù)號(hào)碼進(jìn)行數(shù)據(jù)庫(kù)切換, 使得之后讀入的鍵值對(duì)可以載入到正確的數(shù)據(jù)庫(kù)中。
key_value_pairs
部分保存了數(shù)據(jù)庫(kù)中的所有鍵值對(duì)數(shù)據(jù), 如果鍵值對(duì)帶有過(guò)期時(shí)間, 那么過(guò)期時(shí)間也會(huì)和鍵值對(duì)保存在一起。 根據(jù)鍵值對(duì)的數(shù)量、類型、內(nèi)容、以及是否有過(guò)期時(shí)間等條件的不同, key_value_pairs
部分的長(zhǎng)度也會(huì)有所不同。
作為例子, 圖 IMAGE_EXAMPLE_OF_DB 展示了 RDB 文件中, 0
號(hào)數(shù)據(jù)庫(kù)的結(jié)構(gòu)。
另外, 圖 IMAGE_RDB_WITH_DB_0_AND_DB_3 則展示了一個(gè)完整的 RDB 文件, 文件中包含了 0
號(hào)數(shù)據(jù)庫(kù)和 3
號(hào)數(shù)據(jù)庫(kù)。
RDB 文件中的每個(gè) key_value_pairs
部分都保存了一個(gè)或以上數(shù)量的鍵值對(duì), 如果鍵值對(duì)帶有過(guò)期時(shí)間的話, 那么鍵值對(duì)的過(guò)期時(shí)間也會(huì)被保存在內(nèi)。
不帶過(guò)期時(shí)間的鍵值對(duì)在 RDB 文件中對(duì)由 TYPE
、 key
、 value
三部分組成, 如圖 IMAGE_KEY_WITHOUT_EXPIRE_TIME 所示。
TYPE
記錄了 value
的類型, 長(zhǎng)度為 1
字節(jié), 值可以是以下常量的其中一個(gè):
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
以上列出的每個(gè) TYPE
常量都代表了一種對(duì)象類型或者底層編碼, 當(dāng)服務(wù)器讀入 RDB 文件中的鍵值對(duì)數(shù)據(jù)時(shí), 程序會(huì)根據(jù) TYPE
的值來(lái)決定如何讀入和解釋 value
的數(shù)據(jù)。
key
和 value
分別保存了鍵值對(duì)的鍵對(duì)象和值對(duì)象:
key
總是一個(gè)字符串對(duì)象, 它的編碼方式和 REDIS_RDB_TYPE_STRING
類型的 value
一樣。 根據(jù)內(nèi)容長(zhǎng)度的不同, key
的長(zhǎng)度也會(huì)有所不同。TYPE
類型的不同, 以及保存內(nèi)容長(zhǎng)度的不同, 保存 value
的結(jié)構(gòu)和長(zhǎng)度也會(huì)有所不同, 本節(jié)稍后會(huì)詳細(xì)說(shuō)明每種 TYPE
類型的value
結(jié)構(gòu)保存方式。帶有過(guò)期時(shí)間的鍵值對(duì)在 RDB 文件中的結(jié)構(gòu)如圖 IMAGE_KEY_WITH_EXPIRE_TIME
所示。
帶有過(guò)期時(shí)間的鍵值對(duì)中的 TYPE
、 key
、 value
三個(gè)部分的意義, 和前面介紹的不帶過(guò)期時(shí)間的鍵值對(duì)的 TYPE
、 key
、 value
三個(gè)部分的意義完全相同, 至于新增的 EXPIRETIME_MS
和 ms
, 它們的意義如下:
EXPIRETIME_MS
常量的長(zhǎng)度為 1
字節(jié), 它告知讀入程序, 接下來(lái)要讀入的將是一個(gè)以毫秒為單位的過(guò)期時(shí)間。ms
是一個(gè) 8
字節(jié)長(zhǎng)的帶符號(hào)整數(shù), 記錄著一個(gè)以毫秒為單位的 UNIX 時(shí)間戳, 這個(gè)時(shí)間戳就是鍵值對(duì)的過(guò)期時(shí)間。作為例子, 圖 IMAGE_EXAMPLE_OF_KEY_WITHOUT_EXPIRE_TIME 展示了一個(gè)沒(méi)有過(guò)期時(shí)間的字符串鍵值對(duì)。
圖 IMAGE_EXAMPLE_OF_KEY_WITH_EXPIRE_TIME 展示了一個(gè)帶有過(guò)期時(shí)間的集合鍵值對(duì), 其中鍵的過(guò)期時(shí)間為 1388556000000
(2014 年 1 月 1 日零時(shí))。
RDB 文件中的每個(gè) value
部分都保存了一個(gè)值對(duì)象, 每個(gè)值對(duì)象的類型都由與之對(duì)應(yīng)的 TYPE
記錄, 根據(jù)類型的不同, value
部分的結(jié)構(gòu)、長(zhǎng)度也會(huì)有所不同。
在接下來(lái)的各個(gè)小節(jié)中, 我們將分別介紹各種不同類型的值對(duì)象在 RDB 文件中的保存結(jié)構(gòu)。
注意
本節(jié)接下來(lái)說(shuō)到的各種 REDIS_ENCODING_*
編碼曾經(jīng)在《對(duì)象》一章中介紹過(guò), 如果忘記了可以去回顧一下。
如果 TYPE
的值為 REDIS_RDB_TYPE_STRING
, 那么 value
保存的就是一個(gè)字符串對(duì)象, 字符串對(duì)象的編碼可以是 REDIS_ENCODING_INT
或者REDIS_ENCODING_RAW
。
如果字符串對(duì)象的編碼為 REDIS_ENCODING_INT
, 那么說(shuō)明對(duì)象中保存的是長(zhǎng)度不超過(guò) 32
位的整數(shù), 這種編碼的對(duì)象將以圖 IMAGE_INT_ENCODING_STRING 所示的結(jié)構(gòu)保存。
其中, ENCODING
的值可以是 REDIS_RDB_ENC_INT8
、 REDIS_RDB_ENC_INT16
或者 REDIS_RDB_ENC_INT32
三個(gè)常量的其中一個(gè), 它們分別代表 RDB 文件使用 8
位(bit)、 16
位或者 32
位來(lái)保存整數(shù)值 integer
。
舉個(gè)例子, 如果字符串對(duì)象中保存的是可以用 8
位來(lái)保存的整數(shù) 123
, 那么這個(gè)對(duì)象在 RDB 文件中保存的結(jié)構(gòu)將如圖 IMAGE_EXAMPLE_OF_INT_ENCODING_STRING 所示。
如果字符串對(duì)象的編碼為 REDIS_ENCODING_RAW
, 那么說(shuō)明對(duì)象所保存的是一個(gè)字符串值, 根據(jù)字符串長(zhǎng)度的不同, 有壓縮和不壓縮兩種方法來(lái)保存這個(gè)字符串:
20
字節(jié), 那么這個(gè)字符串會(huì)直接被原樣保存。20
字節(jié), 那么這個(gè)字符串會(huì)被壓縮之后再保存。注意
以上兩個(gè)條件是在假設(shè)服務(wù)器打開(kāi)了 RDB 文件壓縮功能的情況下進(jìn)行的, 如果服務(wù)器關(guān)閉了 RDB 文件壓縮功能, 那么 RDB 程序總以無(wú)壓縮的方式保存字符串值。
具體信息可以參考 redis.conf
文件中關(guān)于 rdbcompression
選項(xiàng)的說(shuō)明。
對(duì)于沒(méi)有被壓縮的字符串, RDB 程序會(huì)以圖 IMAGE_NON_COMPRESS_STRING 所示的結(jié)構(gòu)來(lái)保存該字符串。
其中, string
部分保存了字符串值本身,而 len
保存了字符串值的長(zhǎng)度。
對(duì)于壓縮后的字符串, RDB 程序會(huì)以圖 IMAGE_COMPRESSED_STRING 所示的結(jié)構(gòu)來(lái)保存該字符串。
其中, REDIS_RDB_ENC_LZF
常量標(biāo)志著字符串已經(jīng)被 LZF 算法(http://liblzf.plan9.de)壓縮過(guò)了, 讀入程序在碰到這個(gè)常量時(shí), 會(huì)根據(jù)之后的 compressed_len
、 origin_len
和 compressed_string
三部分, 對(duì)字符串進(jìn)行解壓縮: 其中 compressed_len
記錄的是字符串被壓縮之后的長(zhǎng)度, 而 origin_len
記錄的是字符串原來(lái)的長(zhǎng)度, compressed_string
記錄的則是被壓縮之后的字符串。
圖 IMAGE_EXAMPLE_OF_NON_COMPRESS_STRING 展示了一個(gè)保存無(wú)壓縮字符串的例子, 其中字符串的長(zhǎng)度為 5
, 字符串的值為 "hello"
。
圖 IMAGE_EXAMPLE_OF_COMPRESS_STRING 展示了一個(gè)壓縮后的字符串示例, 從圖中可以看出, 字符串原本的長(zhǎng)度為 21
, 壓縮之后的長(zhǎng)度為6
, 壓縮之后的字符串內(nèi)容為 "?aa???"
, 其中 ?
代表的是無(wú)法用字符串形式打印出來(lái)的字節(jié)。
如果 TYPE
的值為 REDIS_RDB_TYPE_LIST
, 那么 value
保存的就是一個(gè) REDIS_ENCODING_LINKEDLIST
編碼的列表對(duì)象, RDB 文件保存這種對(duì)象的結(jié)構(gòu)如圖 IMAGE_LINKEDLIST_ENCODING_LIST 所示。
list_length
記錄了列表的長(zhǎng)度, 它記錄列表保存了多少個(gè)項(xiàng)(item), 讀入程序可以通過(guò)這個(gè)長(zhǎng)度知道自己應(yīng)該讀入多少個(gè)列表項(xiàng)。
圖中以 item
開(kāi)頭的部分代表列表的項(xiàng), 因?yàn)槊總€(gè)列表項(xiàng)都是一個(gè)字符串對(duì)象, 所以程序會(huì)以處理字符串對(duì)象的方式來(lái)保存和讀入列表項(xiàng)。
作為示例, 圖 IMAGE_EXAMPLE_OF_LINKEDLIST_ENCODING_LIST 展示了一個(gè)包含三個(gè)元素的列表。
結(jié)構(gòu)中的第一個(gè)數(shù)字 3
是列表的長(zhǎng)度, 之后跟著的分別是第一個(gè)列表項(xiàng)、第二個(gè)列表項(xiàng)和第三個(gè)列表項(xiàng), 其中:
5
, 內(nèi)容為字符串 "hello"
。5
, 內(nèi)容為字符串 "world"
。1
, 內(nèi)容為字符串 "!"
。如果 TYPE
的值為 REDIS_RDB_TYPE_SET
, 那么 value
保存的就是一個(gè) REDIS_ENCODING_HT
編碼的集合對(duì)象, RDB 文件保存這種對(duì)象的結(jié)構(gòu)如圖 IMAGE_HT_ENCODING_SET 所示。
其中, set_size
是集合的大小, 它記錄集合保存了多少個(gè)元素, 讀入程序可以通過(guò)這個(gè)大小知道自己應(yīng)該讀入多少個(gè)集合元素。
圖中以 elem
開(kāi)頭的部分代表集合的元素, 因?yàn)槊總€(gè)集合元素都是一個(gè)字符串對(duì)象, 所以程序會(huì)以處理字符串對(duì)象的方式來(lái)保存和讀入集合元素。
作為示例, 圖 IMAGE_EXAMPLE_OF_HT_SET 展示了一個(gè)包含四個(gè)元素的集合。
結(jié)構(gòu)中的第一個(gè)數(shù)字 4
記錄了集合的大小, 之后跟著的是集合的四個(gè)元素:
5
,值為 "apple"
。6
,值為 "banana"
。3
,值為 "cat"
。3
,值為 "dog"
。如果 TYPE
的值為 REDIS_RDB_TYPE_HASH
, 那么 value
保存的就是一個(gè) REDIS_ENCODING_HT
編碼的集合對(duì)象, RDB 文件保存這種對(duì)象的結(jié)構(gòu)如圖 IMAGE_HT_HASH 所示:
hash_size
記錄了哈希表的大小, 也即是這個(gè)哈希表保存了多少鍵值對(duì), 讀入程序可以通過(guò)這個(gè)大小知道自己應(yīng)該讀入多少個(gè)鍵值對(duì)。key_value_pair
開(kāi)頭的部分代表哈希表中的鍵值對(duì), 鍵值對(duì)的鍵和值都是字符串對(duì)象, 所以程序會(huì)以處理字符串對(duì)象的方式來(lái)保存和讀入鍵值對(duì)。結(jié)構(gòu)中的每個(gè)鍵值對(duì)都以鍵緊挨著值的方式排列在一起, 如圖 IMAGE_KEY_VALUE_PAIR_OF_HT_HASH 所示。
因此, 從更詳細(xì)的角度看, 圖 IMAGE_HT_HASH 所展示的結(jié)構(gòu)可以進(jìn)一步修改為圖 IMAGE_DETIAL_HT_HASH 。
作為示例, 圖 IMAGE_EXAMPLE_OF_HT_HASH 展示了一個(gè)包含兩個(gè)鍵值對(duì)的哈希表。
在這個(gè)示例結(jié)構(gòu)中, 第一個(gè)數(shù)字 2
記錄了哈希表的鍵值對(duì)數(shù)量, 之后跟著的是兩個(gè)鍵值對(duì):
1
的字符串 "a"
, 值是長(zhǎng)度為 5
的字符串 "apple"
。1
的字符串 "b"
, 值是長(zhǎng)度為 6
的字符串 "banana"
。如果 TYPE
的值為 REDIS_RDB_TYPE_ZSET
, 那么 value
保存的就是一個(gè) REDIS_ENCODING_SKIPLIST
編碼的有序集合對(duì)象, RDB 文件保存這種對(duì)象的結(jié)構(gòu)如圖 IMAGE_SKIPLIST_ZSET 所示。
sorted_set_size
記錄了有序集合的大小, 也即是這個(gè)有序集合保存了多少元素, 讀入程序需要根據(jù)這個(gè)值來(lái)決定應(yīng)該讀入多少有序集合元素。
以 element
開(kāi)頭的部分代表有序集合中的元素, 每個(gè)元素又分為成員(member)和分值(score)兩部分, 成員是一個(gè)字符串對(duì)象, 分值則是一個(gè) double
類型的浮點(diǎn)數(shù), 程序在保存 RDB 文件時(shí)會(huì)先將分值轉(zhuǎn)換成字符串對(duì)象, 然后再用保存字符串對(duì)象的方法將分值保存起來(lái)。
有序集合中的每個(gè)元素都以成員緊挨著分值的方式排列, 如圖 IMAGE_MEMBER_AND_SCORE_OF_ZSET 所示。
因此, 從更詳細(xì)的角度看, 圖 IMAGE_SKIPLIST_ZSET 所展示的結(jié)構(gòu)可以進(jìn)一步修改為圖 IMAGE_DETIAL_SKIPLIST_ZSET 。
作為示例, 圖 IMAGE_EXAMPLE_OF_SKIPLIST_ZSET 展示了一個(gè)帶有兩個(gè)元素的有序集合。
在這個(gè)示例結(jié)構(gòu)中, 第一個(gè)數(shù)字 2
記錄了有序集合的元素?cái)?shù)量, 之后跟著的是兩個(gè)有序集合元素:
2
的字符串 "pi"
, 分值被轉(zhuǎn)換成字符串之后變成了長(zhǎng)度為 4
的字符串 "3.14"
。1
的字符串 "e"
, 分值被轉(zhuǎn)換成字符串之后變成了長(zhǎng)度為 3
的字符串 "2.7"
。如果 TYPE
的值為 REDIS_RDB_TYPE_SET_INTSET
, 那么 value
保存的就是一個(gè)整數(shù)集合對(duì)象, RDB 文件保存這種對(duì)象的方法是, 先將整數(shù)集合轉(zhuǎn)換為字符串對(duì)象, 然后將這個(gè)字符串對(duì)象保存到 RDB 文件里面。
如果程序在讀入 RDB 文件的過(guò)程中, 碰到由整數(shù)集合對(duì)象轉(zhuǎn)換成的字符串對(duì)象, 那么程序會(huì)根據(jù) TYPE
值的指示, 先讀入字符串對(duì)象, 再將這個(gè)字符串對(duì)象轉(zhuǎn)換成原來(lái)的整數(shù)集合對(duì)象。
如果 TYPE
的值為 REDIS_RDB_TYPE_LIST_ZIPLIST
、 REDIS_RDB_TYPE_HASH_ZIPLIST
或者 REDIS_RDB_TYPE_ZSET_ZIPLIST
, 那么 value
保存的就是一個(gè)壓縮列表對(duì)象, RDB 文件保存這種對(duì)象的方法是:
如果程序在讀入 RDB 文件的過(guò)程中, 碰到由壓縮列表對(duì)象轉(zhuǎn)換成的字符串對(duì)象, 那么程序會(huì)根據(jù) TYPE
值的指示, 執(zhí)行以下操作:
TYPE
的值,設(shè)置壓縮列表對(duì)象的類型: 如果 TYPE
的值為 REDIS_RDB_TYPE_LIST_ZIPLIST
, 那么壓縮列表對(duì)象的類型為列表; 如果TYPE
的值為 REDIS_RDB_TYPE_HASH_ZIPLIST
, 那么壓縮列表對(duì)象的類型為哈希表; 如果 TYPE
的值為 REDIS_RDB_TYPE_ZSET_ZIPLIST
, 那么壓縮列表對(duì)象的類型為有序集合。從步驟 2 可以看出, 由于 TYPE
的存在, 即使列表、哈希表和有序集合三種類型都使用壓縮列表來(lái)保存, RDB 讀入程序也總可以將讀入并轉(zhuǎn)換之后得出的壓縮列表設(shè)置成原來(lái)的類型。
更多建議: