美文网首页
RDB FILE FORMAT

RDB FILE FORMAT

作者: ElephantKing | 来源:发表于2022-02-15 17:38 被阅读0次

    一个完整的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

    相关文章

      网友评论

          本文标题:RDB FILE FORMAT

          本文链接:https://www.haomeiwen.com/subject/mtsllrtx.html