一个完整的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 ==> 未出现过
以下是打的草稿以及做的试验
总纲要:
- 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 - 各类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 + leninteger所占字节数
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
网友评论