RDB FILE FORMAT

一个完整的RDB File二进制展示

0000000 R E D I S 0 0 0 9 372 \t r e d i s
0000020 - v e r 005 6 . 0 . 6 372 \n r e d i
0000040 s - b i t s 300 @ 372 005 c t i m e 302
0000060 * O \n b 372 \b u s e d - m e m 302 300
0000100 3 \r \0 372 \f a o f - p r e a m b l
0000120 e 300 \0 376 \0 373 002 \0 \0 \n P S F _ K 1
0000140 _ P S F \n P S F _ V 1 _ P S F \0
0000160 \n P S F _ K 2 _ P S F \n P S F _
0000200 V 2 _ P S F 377 310 O 023 326 362 232 330 326

RDB文件中可以有什么?

1. 常量字符串,如文件最开头的REDIS0009
2. 长度编码,在需要长度或者一个整数时:先读取2bit
    00:后续6bit的值就是需要的整数
    01:后续14bit的值就是需要的整数
    10;舍弃6bit,再后续4字节的值就是需要的整数
    11:特殊编码,视后续6bit值而定,情况如下:
        000000:后续一个字节是整数
        000001:后续两个字节是整数
        000010:后续四个字节是整数
        000011:后续是一个LZF算法压缩的字符串
3. 字符串编码的值,在需要一个字符串编码的值时:
    length prefixed string : length encoding + raw char[]
    integer string : length encoding + X Byte, low 6bit of length encoding, if low 6bit is 0 => 1B 1 => 2B 2 => 4B
    LZF compressed string : 11 000011
4. 解析引导头字节,占1B,如下:
    0xFA(372):辅助域,FA $string_encode_key $string_encode_val,其中string_encode_key string_encode_val都是字符串编码的值
    0xFB:字典和过期字典的大小,FB $length_encode_int $length_encode_int,其中$length_encode_int $length_encode_in都是长度编码
    0xFC:FC $unsigned_long $string_encode_key $encoded_value,这里$unsigned_long是定长8B时间戳,$encoded_value就可是是各种其他类型了
    0xFD:同0xFC,只是4B的秒级时间戳
    0xFE:某个数据库存档的开始,FE $length_encode_int
    0xFF:RDB文件即将结束,后面仅8B的check_sum了
    以上解析引导头字节后面所紧跟的字节的解析方式是固定的,但是还没涉及到数据库的key-value存储,见下文:
5. 在0xFE $length_encode_int后,紧跟的就是该号对应数据库的key-value。所以RDB文件的大致结构如下:
    常量头部
    0xFA $string_encode_key $string_encode_val
    0xFA $string_encode_key $string_encode_val
    0xFA $string_encode_key $string_encode_val
    0xFE $length_encode_int
    ENCODE_TYPE $string_encode_key $encoded_value
    ENCODE_TYPE $string_encode_key $encoded_value
    ENCODE_TYPE $string_encode_key $encoded_value
    0xFE $length_encode_int
    ENCODE_TYPE $string_encode_key $encoded_value
    ENCODE_TYPE $string_encode_key $encoded_value
    ENCODE_TYPE $string_encode_key $encoded_value
    0xFF + check_sum(8B)

各类型编码解析

    encode value,数据库键总是字符串编码ENCODE_TYPE(1B),但是值却是多样的,除了已经介绍的字符串,还有如下:
    #define RDB_TYPE_STRING 0
    #define RDB_TYPE_LIST   1
    #define RDB_TYPE_SET    2
    #define RDB_TYPE_ZSET   3
    #define RDB_TYPE_HASH   4

    #define RDB_TYPE_HASH_ZIPMAP    9
    #define RDB_TYPE_LIST_ZIPLIST  10
    #define RDB_TYPE_SET_INTSET    11
    #define RDB_TYPE_ZSET_ZIPLIST  12
    #define RDB_TYPE_HASH_ZIPLIST  13
    #define RDB_TYPE_LIST_QUICKLIST 14
    #define RDB_TYPE_STREAM_LISTPACKS 15
    
    RDB_TYPE_STRING => string:如你需要读取一个string时,那就按照[字符串编码的值]去读取就行了,例如:
        例1 ==> 命令"set 123 456"产生的二进制 \0 300 { 301 310 001 377,由第一个字节\0可知,这是一个字符串键,
                那么接下来应该解析的是字符串key,读取一个字节300,其二进制是11000000,这是特殊编码的情形1,后续
                的一个字节就是需要的,即{,其二进制是123,这样就解析出了key
                接下来是value,读取一个字节301,其二进制是11000001,这是特殊编码的情形2,后续2个字节是需要的,
                即001 377,转换后即456
        例2 ==> 命令"set k1 v1"产生的二进制 \0 002 k 1 002 v 1,由第一个字节\0可知,这是一个字符串键,
                那么接下来应该解析的是字符串key,读取一个字节002,其二进制是00000010,因为我们需要的是一个sting,
                由[字符串编码的值]的方式可知,这是一个length-prefixed-string,002就是其长度,那么往后读取两个字节k 1,
                就是key="k1",同理可以得到其value="v1"
        例3 ==> 命令"set k5 v555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555v"
                产生的二进制是 \0 002 k 5 303 \t @ \ 001 v 5 340 O \0 001 5 v,对key的解析就不介绍了,下面介绍value,
                读取一个字节303,其二进制是11000011,这是特殊编码的情形4,也就是LZF压缩的字符串,其结构如下:
                    len_after_compress + len_before_compress + string_after_compress
                其中len_after_compress是经过压缩后的长度,len_before_compress是压缩前的长度,string_after_compress是压缩后的字符串,验证如下:
                字节303后需要读取len_after_compress了,先读取一个字节\t,二进制是00001001,显然是长度编码的情况1,解析后为9,
                接下来解析len_before_compress,也是一个长度,读取一个字节@,其二进制是01000000,显然是长度编码的情况2,所以接下来的14bit才是长度
                即01-000000 \,查ASCII表可得到长度为92,正是v555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555v的长度
                最后便是压缩后的字符串了,其具体值已经无法解释(经过压缩了),但是长度是len_after_compress=9字节,即001 v 5 340 O \0 001 5 v
    RDB_TYPE_LIST => list:有了string的例子,理解list就简单多了,但是未经试验(lpush后都是RDB_TYPE_LIST_QUICKLIST),所以仅列出结构
                001 + key + value,其中value是:length-encoding + N个string encoding
    RDB_TYPE_SET => set:同list,也未经试验(sadd后要么是intset,要么是hashtable)
    RDB_TYPE_ZSET => zset:
    RDB_TYPE_HASH => hash:结构如下
                004 + key + size + field-pair,其中size是hash表中的元素个数,后面跟随size个field-pair
        例1 ==> 命令"hmset h k1 v1 k2 v2 k3 v3 k4 v4 k5 v555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555v"
                产生的二进制如下,开干
                    0000280                         004 001   h 005 002   k   4 002   v   4
                    0000300 002   k   1 002   v   1 002   k   2 002   v   2 002   k   3 002
                    0000320   v   3 002   k   5 303  \t   @   \ 001   v   5 340   O  \0 001
                    0000340   5   v 377
                读取004,可知这是一个hash键,那么接下来就是读取key,轻车熟路001 h,长度为1的字符串h,
                接下来是hash表中元素个数,读取长度,easy,读取一个字节005,长度编码情况1,即有5个元素,可知后续要连续读取5组field-value,不再赘述,仅列出如下:
                002   k   4 002   v   4
                002   k   1 002   v   1 
                002   k   2 002   v   2 
                002   k   3 002   v   3
                002   k   5 303  \t   @   \ 001   v   5 340   O  \0 001 5   v
                正是k1=v1 k2=v2 k3=v3 k4=v4,而k5就是字符串部分的例3
    RDB_TYPE_HASH_ZIPMAP ==> zipmap:未出现过
    RDB_TYPE_LIST_ZIPLIST ==> ziplist:结构如下
                012 + key +  len + raw_string
                其中raw_string = zlbyte(4B) + zltail(4B) + zllen(2B) + entrys + ... + EOF,entry = pre_entry_len + encoding + content
    RDB_TYPE_SET_INTSET ==> \v + key + size + encoding(4B) + len(4B) + len*integer
                            其中size是encoding + len + len*integer所占字节数
                            encoding是整数集合的编码,即intset.encoding,指导如何解析intset.contents
                            len是intset.len,表示contents中装的整数个数,有了这两个数就能解析intset.contents了
                            struct intset {
                                int encoding;
                                int len;
                                uint8_t contents[];
                            };
        例1 ==> intset:命令"sadd sad 123 456"产生的二进制如下: 实际从\v 003处开始,       
                0000120   e 300  \0 376  \0 373 001  \0  \v 003   s   a   d  \f 002  \0
                0000140  \0  \0 002  \0  \0  \0   {  \0 310 001 377 272 256 212   .   X
                0000160 244   + 244
                读取\v,其值为11,即RDB_TYPE_SET_INTSET,接下来读取key,003   s   a   d,
                接下来读取size,读取一个字节\f,其二进制是00001100,长度编码情况1,得到12,
                接下来读取encoding(4B 小端机),得到值为2,说明intset中每个元素占2个字节,
                接下来读取len(4B 小端机),得到值为2,说明intset中有两个元素,接下来按2字节截取,得到{ \0 => 123 和 310 001 => 456


    RDB_TYPE_ZSET_ZIPLIST ==> ziplist实现的zset:
    RDB_TYPE_HASH_ZIPLIST ==> ziplist实现的hash:
    RDB_TYPE_LIST_QUICKLIST ==> quicklist:
    RDB_TYPE_STREAM_LISTPACKS ==> 未出现过

以下是打的草稿以及做的试验


总纲要:

  1. RDB中,因为key永远是string,而string的编码只能是如下三种:
    length-prefixed-string :002 k 1
    8 16 32bit integer :300 {
    LZF compressed string :303 \t @ \ 001 v 5 340 O \0 001 5 v
  2. 各类value的编码如下:
    string:string的编码同key
    list:001 + key + value,其中value是:length-encoding + N个string encoding
    set:同list,即002 + key + value
    sorted set:003 + key + len + item-score + ...
    hash:004 + key + key-pair-size + key-pair + ...
    zipmap:
    ziplist:012 + key + len + raw_string,其中raw = zlbyte(4B) + zltail(4B) + zllen(2B) + entrys + ... + EOF
    entry = pre_entry_len + encoding + content
    intset:013 + key + size + encoding + len + leninteger
    其中size是encoding + len + len
    integer所占字节数
    encoding是整数集合的编码,即intset.encoding,指导如何解析intset.contents
    len是intset.len,表示contents中装的整数个数,有了这两个数就能解析intset.contents了
    struct intset {
    int encoding;
    int len;
    uint8_t contents[];
    };
    sorted set in ziplist:同ziplist
    hashmap in ziplist:{"us" => "washington", "india" => "delhi"} => ["us", "washington", "india", "delhi"],然后就像存ziplist一样
    quicklist:016 + key + len + ZIPLIST + ZIPLIST + ...

set 123 456
\0 300 { 301 310 001 377
\0 => RDB_TYPE_STRING
300 => 11 000000 => 后续一个字节是整数,即{ => 123,说明这个string key里其实是一个整数
301 => 11 000001 => 后续两个字节是整数,即310 001 => 456

set k1 v1
\0 002 k 1 002 v 1
\0 => RDB_TYPE_STRING
002 k 1 => 长度为2的字符串
002 v 1 => 长度为2的字符串


数字编码:读取2bit
00:后续6bit是值
01:后续14bit是值
10:舍弃6bit,再后续4字节是值

特殊编码
11:特殊编码,视后续6bit值而定,情况如下:
000000:后续一个字节是整数
000001:后续两个字节是整数
000010:后续四个字节是整数
000011:后续是一个LZF算法压缩的字符串

string encoding
length prefixed string : length encoding + raw char[]
integer string : length encoding + X Byte, low 6bit of length encoding, if low 6bit is 0 => 1B 1 => 2B 2 => 4B
LZF compressed string : 11 000011


hash encoding : 004 + key of hash encoded by length-prefixed-string + length-encoding + [string encoded key + value]

0000280 004 001 h 005 002 k 4 002 v 4
0000300 002 k 1 002 v 1 002 k 2 002 v 2 002 k 3 002
0000320 v 3 002 k 5 303 \t @ \ 001 v 5 340 O \0 001
0000340 5 v 377

004 => RDB_TYPE_HASH
001 h => hash的键明就叫h
005 => 5 => 5对key-pair
002 k 4 002 v 4
002 k 1 002 v 1
002 k 2 002 v 2
002 k 3 002 v 3
002 k 5 303 \t @ \ 001 v 5 340 O \0 001 5 v
着重解释一下:key = k5,其值是value = v555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555v
显然这里进行了LZF的压缩,细看如下:
303 => 11 000011 => 特殊编码,后续是一个LZF压缩的字符串
\t => 9 => 压缩后的长度,即001 v 5 340 O \0 001 5 v,这9个字节是经过LZF压缩后的值
@ \ => 01 000000 0101 1100 => 后14bit表示长度 => 92 => value的长度


372 => FA => 辅助字段
FA $string_encode_key $string_encode_val

解释部分:
372 \t r e d i s - v e r 005 6 . 0 . 6
372标识后续是辅助字段,即将读取两个字符串,这两个字符串都是length-prefix string,
\t对应的十进制是9,表示第一个字符串长度为9,即redis-ver
005对应十进制是5,表示第二个字符串长度为5,即6.0.6

372 \n r e d i s - b i t s 300 @
\n对应十进制10,表示第一个字符串长度为10,即redis-bits
300@对应的二进制是:1100 0000 0100 0000,看前一个字节高两位为11,次6位值为0,表明后续是一个字节的整数,即64

372 005 c t i m e 302 * O \n b
005 => 5 => ctime
302 => 1100 0010,后续4B是整数* O \n b,16进制是2A4F0A62,转为整数实际是:620A4F2A=1644842794,转为时间是Mon Feb 14 20:46:34 CST 2022

372 \b u s e d - m e m 302 300 3 \r \0
\b => 8 => used-mem
302 => 1100 0010,后续4B是整数300 3 \r \0 => C0030D00 => 000D03C0 => 852928,单位不详

372 \f a o f - p r e a m b l e 300 \0
\f => 12 => aof-preamble
300 => 11 000000 后续一个字节是整数\0 => 0


373 => FB => ResizeDB Field
FB
$length_encode_int
$length_encode_int

373 002 \0
002 => 00 000010 => 2 => database hashtable size
\0 => 00 000000 => 0 => expire hashtable size
并且紧接着就是key_value_pairs部分了,key_value_pairs的格式是: TYPE KEY VALUE

字符串对象分析:
\0 \n P S F _ K 1 _ P S F \n P S F _ V 1 _ P S F
\0 \n P S F _ K 2 _ P S F \n P S F _ V 2 _ P S F
\0代表TYPE => RDB_TYPE_STRING => \n => 10 => PSF_K1_PSF
\0代表TYPE => RDB_TYPE_STRING => \n => 10 => PSF_V1_PSF
\0代表TYPE => RDB_TYPE_STRING => \n => 10 => PSF_K2_PSF
\0代表TYPE => RDB_TYPE_STRING => \n => 10 => PSF_V2_PSF

列表对象分析:
0000210 016 \b l
0000220 s t : n a m e 001 # # \0 \0 \0 033 \0 \0
0000240 \0 003 \0 \0 \a n a m e 1 2 3 \t 006 n a
0000260 m e 1 2 \b 005 n a m e 1 377
016 => 14 => RDB_TYPE_LIST_QUICKLIST
\b => 8 => lst:name
001 => 1 => RDB_TYPE_LIST
# => 35 => 接下来是一个35B的字符串,存储时将ZIPLIST转为字符串对象了
# \0 \0 \0 => 35(小端机, ZLBYTE 4B) => ZIPLIST的ZLBYTE=35(ZIPLIST的结构为 ZLBYTE ZLTAIL ZLLEN ENTRYS ZLEND)
033 \0 \0 \0 => 27(小端机, ZLTAIL 4B) => 最后一个entry的首地址(即0000229(#位置) + 27 => 0000264(\b位置))
003 \0 => 3(小端机, ZLLEN 2B) => 一共三个元素
\0 => 0 => 前一个entry的长度,因为这是第一个entry,所以这个字段为0
\a => 7 => name123
\t => 9 => 前一个entry的长度,\0 \a n a m e 1 2 3,指的是这一段的长度
006 => 6 => name12
\b => 8 => 前一个entry的长度,\t 006 n a m e 1 2,指的是这一段的长度
005 => 5 => name1
将上述分节便是:
016
\blst:name
001 #
#\0\0\0 033\0\0\0 003\0 \0\aname123 \t006name12 \b005name1 377


374 => FC => 过期时间(ms),后续8B是时间戳
FC $unsigned_long
$value_type 1B
$string_encode_key
$encoded_value

下面这段不在此例中:通过命令得到set k1 v1 ex 1000
374 250 n 233 370 ~ 001 \0 \0 \0 002 k 1 002 v 1
8B => 250 n 233 370 ~ 001 \0 \0 => 过期时间戳
\0代表TYPE => RDB_TYPE_STRING => 002 => 2 => k1
\0代表TYPE => RDB_TYPE_STRING => 002 => 2 => v1
其格式为:FC TIMESTAMP TYPE KEY VALUE


375 => FD => 过期时间(s),后续4B是时间戳
FC $unsigned_int
$value_type 1B
$string_encode_key
$encoded_value


376 => FE => SELECT DB
FE $length_encoding

376 \0
select 0号DB


377 => FF => EOF
FF 8-byte-checksum

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容