Redis RDB文件格式
Redis的RDB文件是对内存存储的一种表示。这个二进制文件足以完全恢复Redis当时的运行状态。 RDB文件格式针对快速读写进行了优化。LZF压缩被用于减小文件大小。 通常,对象的长度会作为该条记录的前缀,所以在读取对象前,你已经精确地知道了需要分配多少内存。 优化文件的快速读写,意味着数据在磁盘中的格式,尽可能的和内存中展示的一样。 这就是RDB文件采用的方法。 因此,你可以在不了解Redis内存数据结构的前提下,解析RDB文件。
整体看,RDB的文件结构如下:
----------------------------#
52 45 44 49 53 # 文件魔术 字符串"REDIS"
30 30 30 33 # RDB的版本号,使用ASCII字符串表示: "0003" = 3
----------------------------
FA # 辅助字段
$string-encoded-key # 可能包含任意元数据信息
$string-encoded-value # 例如Redis的版本、创建时间、已使用的内存等等...
----------------------------
FE 00 # 指明数据库. db = 00
FB # 指明resizedb属性
$length-encoded-int # 相应Hash表的大小
$length-encoded-int # 相应带失效时间的Hash表大小
----------------------------# 键值对统计
FD $unsigned-int # "秒级超时", 紧挨着4个字节组成的无符号整数
$value-type # 1字节指明数据的编码方式
$string-encoded-key # KEY-键,使用Redis字符串编码方式
$encoded-value # VALUE-值,使用$value-type指明的编码方式
----------------------------
FC $unsigned long # "毫秒级超时", 紧挨着8个字节组成的无符号长整数
$value-type # 1字节指明数据的编码方式
$string-encoded-key # KEY-键,使用Redis字符串编码方式
$encoded-value # VALUE-值,使用$value-type指明的编码方式
----------------------------
$value-type # 没有失效时间的KV键值对
$string-encoded-key
$encoded-value
----------------------------
FE $length-encoding # 上一个DB的信息结束,下一个DB的信息。
----------------------------
... # 其他键值对、DB信息...
FF ## RDB文件结束标识
8-byte-checksum ## 8字节的CRC64表示的文件校验和
RDB文件以字符串“REDIS”作为开始。 这是一个快速完整性校验,用于判断是否在处理一个RDB文件。
52 45 44 49 53 # “REDIS”
接下来的4个字节存放了RDB格式的版本号。 这4个字节是ASCII码()的值,然后使用字符串转整型的方式,将之转换成一个整数。
30 30 30 33 # “0003” => Version 3 在ASCII码中’0’ = 十进制48 = 十六禁制30。
ASCII码表
十进制 | ASCII码 | 十进制 | ASCII码 | 十进制 | ASCII码 | 十进制 | ASCII码 | |
---|---|---|---|---|---|---|---|---|
0 | NUT | 32 | (space) | 64 | @ | 96 | 、 | |
1 | SOH | 33 | ! | 65 | A | 97 | a | |
2 | STX | 34 | “ | 66 | B | 98 | b | |
3 | ETX | 35 | # | 67 | C | 99 | c | |
4 | EOT | 36 | $ | 68 | D | 100 | d | |
5 | ENQ | 37 | % | 69 | E | 101 | e | |
6 | ACK | 38 | & | 70 | F | 102 | f | |
7 | BEL | 39 | , | 71 | G | 103 | g | |
8 | BS | 40 | ( | 72 | H | 104 | h | |
9 | HT | 41 | ) | 73 | I | 105 | i | |
10 | LF | 42 | * | 74 | J | 106 | j | |
11 | VT | 43 | + | 75 | K | 107 | k | |
12 | FF | 44 | , | 76 | L | 108 | l | |
13 | CR | 45 | - | 77 | M | 109 | m | |
14 | SO | 46 | . | 78 | N | 110 | n | |
15 | SI | 47 | / | 79 | O | 111 | o | |
16 | DLE | 48 | 0 | 80 | P | 112 | p | |
17 | DCI | 49 | 1 | 81 | Q | 113 | q | |
18 | DC2 | 50 | 2 | 82 | R | 114 | r | |
19 | DC3 | 51 | 3 | 83 | S | 115 | s | |
20 | DC4 | 52 | 4 | 84 | T | 116 | t | |
21 | NAK | 53 | 5 | 85 | U | 117 | u | |
22 | SYN | 54 | 6 | 86 | V | 118 | v | |
23 | TB | 55 | 7 | 87 | W | 119 | w | |
24 | CAN | 56 | 8 | 88 | X | 120 | x | |
25 | EM | 57 | 9 | 89 | Y | 121 | y | |
26 | SUB | 58 | : | 90 | Z | 122 | z | |
27 | ESC | 59 | ; | 91 | [ | 123 | { | |
28 | FS | 60 | < | 92 | / | 124 | ||
29 | GS | 61 | = | 93 | ] | 125 | } | |
30 | RS | 62 | > | 94 | ^ | 126 | ` | |
31 | US | 63 | ? | 95 | _ | 127 | DEL |
在初始化的头部后,每个部分都由一个特殊操作码引入。可用的操作码如下:
Byte | 名称 | 描述 |
---|---|---|
0xFF | EOF | RDB文件的结尾 |
0xFE | SELECTDB | 数据库选择器 |
0xFD | EXPIRETIME | 秒级别的失效时间, 参照下面的Key失效时间戳介绍 |
0xFC | EXPIRETIMEMS | 毫秒级别的失效时间, 参照下面的Key失效时间戳介绍 |
0xFB | RESIZEDB | 主键值空间和失效键值的哈希表大小。参照下面的Resizedb信息介绍 |
0xFA | AUX | 辅助字段。任意键值对,参照下面的辅助字段介绍 |
一个Redis实例可能有多个数据库。 一个字节0xFE用于标识数据库选择器部分的开始。 在该字节后,一个变长的字段表示数据库的索引值。 了解如何读取该数据库索引值,请参照长度编码。
这个操作码是RDB版本7引入的。 这部分包含两个编码后的值,用于加速RDB的加载,避免在加载过程中额外的调整hash空间(resize)和rehash操作。 在操作码之后的两个值是:
这个操作码是RDB版本7引入的。 操作码后是两个Redis字符串,表示设置的KV键值对。 未知的字段会被解析器忽略。
目前实现的配置有:
在数据库选择器信息后,这个文件包含了一系列的KV键值对序列。 每个KV键值对由4部分组成:
该部分由一个字节标识开始,该表示可以是以下两种之一:
在导入过程中,如果key失效了,必须被忽略掉,即不加载。
一个字节来表示value使用的编码方式。
Key被当作一个Redis字符串进行编码。参照String编码部分,了解key是如何编码的。
值的编码方式根据Value Type字段决定。参照编码部分。
长度编码用于存放,在流中下一个对象的长度。 长度编码是一个变长的字节编码,目的是尽可能的使用少量的字节。 长度编码的工作原理:从流中读取一个字节,比较两个最高有效比特(bit)位:
比特 | 如何解析 |
---|---|
00 | 接下来的6个bit表示长度 |
01 | 接下来的6个bit,加上再读取一个字节(即8bit),组成的14 bit表示长度。 |
10 | 忽略该字节剩下的6个bit。再从数据流中读取4个字节,表示长度。 |
11 | 接下来读取的对象使用了特殊编码。该字节剩余的6个bit表示编码格式。可能存放整数型或者字符串,参照String编码。 |
结论:
Redis字符串是二进制安全-这意味着你可以存放任意数据。 字符串没有任何的特殊字符串作为结尾标记。 最好将Redis的字符串看作一个字节数组。
Redis有三种String类型:
长度作为前缀的字符串,格式非常简单,首先,字符串的字节长度使用长度编码 之后存放的就是该字符串的原始字节数组。
首先按照长度编码读取,开始的两个bit是11(二进制)。 在这种情况下,剩下的6个字节值的如果情况:
首先阅读长度编码章节,特别是开始的两个bit是11(二进制)的时候。 在这种情况下,读取剩下的6个bit值。如果这6bit的值是3,则表示接下来是一个压缩的字符串。
压缩字符串的读取方式如下:
一个Redis的List使用一系列字符串表示。
set的编码方式和list一模一样。
注意:由于分值的精度可能失真。Redis使用双精度存放该分值。 注意:不能确保读取到的组合是有序的。
示例:
04
01 63 12 34 2E 30 31 39 39 39 39 39 39 39 39 39 39 39 39 39 36
01 64 FE
01 61 12 33 2E 31 38 39 39 39 39 39 39 39 39 39 39 39 39 39 39
01 65 FF
最后sorted set为:
{
"e" => "-inf",
"a" => "3.189999999999999",
"c" => "4.0199999999999996",
"d" => "+inf"
}
示例:
2 us washington india delhi
表示:
{
"us" => "washington",
"india" => "delhi"
}
一个ZipMap就是一个HashMap被序列化成的一个字符串。 实质上,键值对是被顺序存储的。 在该结构中查找一个key的算法时间复杂度是O(N)。 当键值对的数量较少时,使用该结构而不是字典结构。 解析一个ZipMap,首先使用String编码从流中读取一个字符串。这个字符串就表示该ZipMap。
一个ZipMap使用字符串的表达形式如下:
<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"<zmend>
示例:
18 02 06 4D 4B 44 31 47 36 01 00 32 05 59 4E 4E 58 4b 04 00 46 37 54 49 FF ..
这样,我们得到ZipMap如下:
{
"MKD1G6" => "2",
"YNNXK" => "F7TI"
}
我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1tl6lbu6kcce