概覽
Header
RDB文件的頭部占用9bytes,前5bytes為Magic String,后4bytes為版本號;
52 45 44 49 53 #"REDIS",就像java的class文件以0xCAFEBABE開頭一樣
30 30 30 36 #RDB版本號,30表示‘0’,版本號為0006=6
注意:版本號是字符串而不是整型;:
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
Body
DB Selector
FE開頭表示后跟表示DB Selector;例如:
FE 00#FE表明數據庫的哪個db,此處為db0
注意:DB Selector長度不固定,具體的編碼方式請參見后文的Length編碼
AUX Fields
FA開頭表示后跟AUX Fields,記錄生成Dump文件的Redis相關信息,例如redis-ver、redis-bits、used-mem、aof-preamble和repl-id等;
這些信息采用String編碼;
注意:redis3.0版本的RDB版本號為6,redis3.2的版本號為7;
Key-Value
key-value有三種格式:
-
expire為second
FD $unsigned int #失效時間(秒),4個字節 $value-type #1個字節,表明數據類型:set,map等 $string-encoded-key #key值,字符串類型 $encoded-value #value,編碼方式和類型有關
-
expire為millisecond
FC $unsigned long #失效時間(毫秒),8個字節 $value-type #數據類型,1個字節 $string-encoded-key #key,字符串類型 $encoded-value #value,編碼方式和類型有關
-
無expire
$value-type #數據類型,1個字節 $string-encoded-key #key,字符串類型 $encoded-value #value,編碼方式和類型有關
Footer
FF #RDB文件的結束
8byte checksum #循環冗余校驗碼,Redis采用crc-64-jones算法,初始值為0
編碼算法說明
Length編碼
長度采用BigEndian格式存儲,為無符號整數
- 如果以"00"開頭,那么接下來的6個bit表示長度;
- 如果以“01”開頭,那么接下來的14個bit表示長度;
- 如果以"10"開頭,該byte的剩余6bit廢棄,接著讀入4個bytes表示長度(BigEndian);
- 如果以"11"開頭,那么接下來的6個bit表示特殊的編碼格式,一般用來存儲數字:
- 0表示用接下來的1byte表示長度
- 1表示用接下來的2bytes表示長度;
- 2表示用接下來的4bytes表示長度;
String編碼
該編碼方式首先采用Length編碼 進行解析:
- 從上面的Length編碼知道,如果以"00","01","10"開頭,首先讀取長度;然后從接下來的內容中讀取指定長度的字符;
- 如果以"11"開頭,而且接下來的6個字節為“0”、“1”和“2”,那么直接讀取接下來的1,2,4bytes做為字符串的內容(實際上存儲的是數字,只不過按照字符串的格式存儲);
- 如果以“11”開頭,而且接下來的6個字節為"3",表明采用LZF壓縮字符串格式:
LZF編碼的解析步驟為:
- 首先采用Length編碼讀取壓縮后字符串的長度clen;
- 接著采用Length編碼讀取壓縮前的字符串長度;
- 讀取clen長度的字節,并采用lzf算法解壓得到原始的字符串
Score編碼
- 讀取1個字節,如果為255,則返回負無窮;
- 如果為254,返回正無窮;
- 如果為253,返回非數字;
- 否則,將該字節的值做為長度,讀取該長度的字節,將結果做為分值;
Value編碼
Redis中的value編碼包括如下類型:
類型名稱 | 類型代碼 |
---|---|
String Encoding | 0 |
List Encoding | 1 |
Set Encoding | 2 |
Sorted Set Encoding | 3 |
Hash Encoding | 4 |
Zipmap Encoding | 9 |
Ziplist Encoding | 10 |
Intset Encoding | 11 |
Sorted Set in Ziplist Encoding | 12 |
Hashmap in Ziplist Encoding | 13 |
其中String編碼在前面已經介紹過,接下來逐一介紹其他的9種編碼方式;
List
- 首先用Length編碼讀取List的長度lsize;
- 采用String編碼讀取lsize個字符串
Set
同List
Sorted Set
- 首先用Length編碼讀取Sorted Set的長度zsize;
- 采用String編碼讀取字符串,采用Score編碼讀取分值;
- 循環讀取zsize次;
Hash
- 采用Length編碼讀取Hash的大小hsize;
- 采用String編碼讀取2*hsize的字符串,按照key,value的方式組裝成Map
Zipmap
用于存儲hashmap,Redis2.6之后,該編碼被廢棄,轉而采用Ziplist編碼;
采用String編碼讀取整個zipmap字符串,hashmap字符串的格式為:
<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"<zmend>
- zmlen:一個字節,Zipmap的大小;如果>=254,意味著zipmap的大小無法直接獲取到,必須要遍歷整個zipmap才能得到大小;
- len:字符串長度,1或5個字節長度;如果第一個字節在0~252之間,那么長度為第一個字節;如果為253,那么接下來的4個字節表示長度;254和255是無效值;
- free:1字節,表明value空閑的字節數;
- zmend:0xff,表示Zipmap的結尾;
Ziplist
采用String編碼讀取整個ziplist字符串,字符串的格式為:
<zlbytes><zltail><zllen><entry><entry><zlend>
- zlbytes:4字節無符號整數,表示ziplist占用的總字節數;
- zltail:4字節無符號整數(little endian),表示尾元素的偏移量;
- zllen:2字節無符號整數(little endian),表示ziplist中的元素個數, 當元素個數大于65535時,無法用2字節表示,需要遍歷列表獲取元素個數;
- entry:ziplist中的元素;
- zlend:常量(0xff),表示ziplist的結尾;
entry的格式:
<length-prev-entry><encoding><content>
- lenth-prev-entry:如果第一個字節<254,則用1bytes表示長度;否則則用接下來的4bytes(無符號整數)表示長度;
- encoding
- "00"開頭:字符串,用接下來的6bit表示長度;
- "01"開頭:字符串,用接下來的14bit表示長度;
- "10"開頭:字符串,忽略本字節的6bit,用接下來的32bit表示長度;
- "11000000"開頭:整數,內容為接下來的16bit;
- "11010000"開頭:整數,內容為接下來的32bit;
- "11100000"開頭:整數,內容為接下來的64bit;
- "11110000"開頭:整數,內容為接下來的24bit;
- "11111110"開頭:整數,內容為接下來的8bit;
- "1111"開頭 :整數,內容為接下來的4bit的值減去1;
- content
entry內容,它的長度通過前面的encoding確定;
注意:元素長度、內容長度等都是采用Little Endian編碼;
Intset
Intset是一個整數組成的二叉樹;當set的所有元素都是整形的時候,Redis會采用該編碼進行存儲;Inset最大可以支持64bit的整數,做為優化,如果整數可以用更少的字節數表示,Redis可能會用16~32bit來表示;注意的是當插入一個長度不一樣的整數時,有可能會引起整個存儲結構的變化;
由于Intset是一個二叉樹,因此它的元素都是排序過的;
采用String編碼讀取整個intset字符串,字符串的格式為:
<encoding><length-of-contents><contents>
- encoding:32bit的無符號整數;可選值包括2、4和8;表示inset中的每個整數占用的字節數;
- length-of-contents:32bit無符號整數,表示Intset中包含的整數個數;
- contents:整數數組,長度由length-of-contents決定;
Sorted Set in Ziplist Encoding
采用Ziplist編碼,區別在于用兩個entry分別表示元素和分值;
Hashmap in Ziplist Encoding
采用Ziplist編碼,區別在于用兩個entry分別表示key和value;
代碼樣例
代碼樣例請參考github上的例子redis-sync