RDB 是什麼
Redis *.rdb
是內存的二進制文件
,通過 *.rdb 能夠完全回覆 Redis 的運行狀態。
![](http://yano.oss-cn-beijing.aliyuncs.com/blog/
20220218175013.png?x-oss-process=style/yano)
RDB 文件格式
詳細信息可參考:Redis RDB Dump File Format。
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);
RDB_VERSION 詳細信息可參考:Redis RDB Version History
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 編碼在前面已經介紹過,接下來逐一介紹其他的 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;
實際例子
本篇文章在本地安裝並啓動 Redis 服務,保存一個 string 類型的字符串,save 之後查看保存的 rdb 文件的二進制。
安裝、啓動 Redis
下載見:Redis Download
啓動 Redis server:
src/redis-server&
啓動一個 Redis client:
src/redis-cli
保存字符串
127.0.0.1:6379> set name yano
OK
保存 RDB 文件
127.0.0.1:6379> save
80277:M 15 Feb 2022 10:51:07.308 * DB saved on disk
OK
在剛執行 redis-cli 的目錄下,就生成了 rdb 文件,文件名是 dump.rdb。
分析 RDB 文件
使用 hexedit
命令分析 dump.rdb
文件:
hexedit dump.rdb
dump.rdb 文件內容如下:
本篇文章只是分析 rdb 文件的基本結構和格式,只保存了一個最基礎的 string。(圖畫了一個小時😁)RDB 這塊的 Redis 源碼就不分析了,基本上都是按照這個結構來的。
參考鏈接
Redis 源碼簡潔剖析系列
Java 編程思想-最全思維導圖-GitHub 下載鏈接,需要的小夥伴可以自取~
原創不易,希望大家轉載時請先聯繫我,並標註原文鏈接。