RDB文件格式

一、Redis RDB文件

Redis的RDB文件是内存存储的二进制表示。这个二进制文件足以完全恢复Redis的状态。

RDB文件格式针对快速读写进行了优化。在可能的情况下,使用LZF压缩来减小文件大小。一般情况下,对象都以它们的长度为前缀,因此在读取对象之前,您可以确切地知道要分配多少内存。

针对快速读写进行优化,意味着磁盘上的格式应该尽可能接近内存中的表示形式。这是RDB文件采用的方法。因此,如果不了解Redis在内存中的数据结构表示,就无法解析RDB文件。

二、解析RDB的高级算法

从一个较高的维度看,RDB文件的结构如下:

----------------------------#
52 45 44 49 53              # Magic String "REDIS"
30 30 30 33                 # RDB Version Number as ASCII string. "0003" = 3
----------------------------
FA                          # Auxiliary field
$string-encoded-key         # May contain arbitrary metadata
$string-encoded-value       # such as Redis version, creation time, used memory, ...
----------------------------
FE 00                       # Indicates database selector. db number = 00
FB                          # Indicates a resizedb field
$length-encoded-int         # Size of the corresponding hash table
$length-encoded-int         # Size of the corresponding expire hash table
----------------------------# Key-Value pair starts
FD $unsigned-int            # "expiry time in seconds", followed by 4 byte unsigned int
$value-type                 # 1 byte flag indicating the type of value
$string-encoded-key         # The key, encoded as a redis string
$encoded-value              # The value, encoding depends on $value-type
----------------------------
FC $unsigned long           # "expiry time in ms", followed by 8 byte unsigned long
$value-type                 # 1 byte flag indicating the type of value
$string-encoded-key         # The key, encoded as a redis string
$encoded-value              # The value, encoding depends on $value-type
----------------------------
$value-type                 # key-value pair without expiry
$string-encoded-key
$encoded-value
----------------------------
FE $length-encoding         # Previous db ends, next db starts.
----------------------------
...                         # Additional key-value pairs, databases, ...

FF                          ## End of RDB file indicator
8-byte-checksum             ## CRC64 checksum of the entire file.

2.1 Magic Number

文件以“REDIS”这个神奇的字符串开始。这是一个快速的完整性检查,以了解我们正在处理的是一个redis rdb文件。

52 45 44 49 53 # “REDIS”

2.2 RDB 版本号

接下来的4个字节存储rdb格式的版本号。这4个字节被解释为ASCII字符,然后使用字符串到整数的转换转换为整数。

30 30 30 33 # “0003” => Version 3

2.3 操作码

初始标头之后的每个部分由一个特殊的操作码引入。可用的操作码为:

字节名称描述
0xFFEOFRDB文件的结尾
0xFESELECTDB数据库选择器
0xFDEXPIRETIME到期时间(以秒为单位)
0xFCEXPIRETIMEMS到期时间(以毫秒为单位)
0xFBRESIZEDB主键空间的哈希表大小和到期时间
0xFAAUX辅助字段。任意键值设置
2.3.1 数据库选择器

Redis实例可以具有多个数据库。

一个字节0xFE标记数据库选择器的开始。在此字节之后,可变长度字段指示数据库编号。请参阅“ 长度编码 ”部分,以了解如何读取此数据库编号。

2.3.2 Resizeb信息

此操作代码在RDB版本7中引入。

它对两个值进行编码,以通过避免其他大小调整和重新散列来加快RDB加载。操作码后跟两个长度编码的整数,指示:

  • 数据库哈希表大小
  • 过期哈希表大小
2.3.3 辅助字段

此操作代码在RDB版本7中引入。

操作码后跟两个Redis字符串,分别代表设置的键和值。解析器应忽略未知字段。

当前实现了以下设置:

  • redis-ver:编写RDB的Redis版本
  • redis-bits:编写RDB的系统的位体系结构,可以是32或64
  • ctime:RDB的创建时间
  • used-mem:编写RDB的实例的已用内存
2.3.4 键值对

在数据库选择器之后,该文件包含一系列键值对。

每个键值对都有4个部分:

  • key到期时间戳。这是可选的。
  • 1个字节的标志,指示值的类型。
  • key,编码为Redis字符串。请参阅字符串编码。
  • value,根据值类型进行编码的值。请参阅值编码。
key 到期时间戳

该部分以一个字节标志开始。该标志是:

  • 0xFD:以秒为单位指定以下过期值。以下4个字节将Unix时间戳表示为无符号整数。
  • 0xFC:指定以下过期值(以毫秒为单位)。以下8个字节将Unix时间戳表示为无符号长。

在导入过程中,必须丢弃已过期的密钥。

值类型

一个字节的标志指示用于保存值的编码。

  • 0 = 字符串编码
  • 1 = list编码
  • 2 = set编码
  • 3 = sort set编码
  • 4 = hash编码
  • 9 = Zipmap编码
  • 10 = Ziplist编码
  • 11 = Intset编码
  • 12 = 有序集合的Ziplist编码
  • 13 = Hashmap的Ziplist编码(在RDB版本4中引入)
  • 14 = 快速列表的编码(在RDB版本7中引入)

key是以Redis字符串进行编码的。请参阅“ 字符串编码 ”部分,以了解密钥的编码方式。

值是根据先前读取的“ 值类型”进行解析的。

2.4 CRC64校验码

从RDB版本5开始,将8字节CRC64校验和添加到文件末尾。可以通过redis.conf中的参数禁用此校验和。禁用校验和时,此字段将为零。

三、编码方式

3.1 Length Encoding 长度编码

长度编码用于存储流中下一个对象的长度。长度编码是一种可变字节编码,旨在使用尽可能少的字节。

长度编码的工作方式如下:从流中读取一个字节,比较两个最高有效位:

Bits如何解析
00接下来的6位代表长度
01再读取一个字节。组合的14位代表长度
10丢弃剩余的6位。流中接下来的4个字节表示长度
11下一个对象以特殊格式编码。其余6位指示格式。可用于存储数字或字符串,请参见字符串编码

作为这种编码的结果:

  • 最多63个数字(包括63个数字)可以存储在1个字节中
  • 最多包括16383的数字可以存储在2个字节中
  • 最多232-1的数字可以存储在4个字节中

3.2 字符串编码

Redis字符串是二进制安全的——这意味着您可以在其中存储任何内容。它们没有任何特殊的字符串结尾标记。最好将Redis字符串视为字节数组。

Redis中有三种类型的字符串:

  • 长度前缀字符串
  • 8、16或32位整数
  • LZF压缩字符串
3.2.1 长度前缀字符串

长度前缀字符串非常简单。字符串的长度(以字节为单位)首先使用Length Encoding进行编码。此后,将存储字符串的原始字节。

3.2.2 整数作为字符串

首先,阅读“ 长度编码”部分,特别是前两位为11的部分。在这种情况下,将读取剩余的6位。

如果这6位的值是:

  • 0 表示跟随一个8位整数
  • 1 表示跟随一个16位整数
  • 2 表示后跟32位整数
3.2.3 压缩字符串

首先,阅读“ 长度编码”部分,特别是前两位为的部分11。在这种情况下,将读取剩余的6位。如果这6位的值为3,则表示紧随其后的是压缩字符串。

压缩字符串按如下方式进行读取:

  • 使用长度编码从流中读取压缩的长度 clen
  • 使用长度编码从流中读取未压缩的长度
  • 从流中读取下一个 clen 长度的压缩字节
  • 最后,使用LZF算法解压这些字节

3.3 列表编码

Redis列表表示为字符串序列。

  • 首先,size使用Length Encoding从流中读取列表的大小
  • 接下来,size使用String Encoding从流中读取字符串
  • 然后使用这些字符串重新构建列表

3.4 集合编码

集合编码与列表编码完全相同。

3.5 有序集合编码

  • 首先,使用长度编码从流中读取有序集合的数量大小size
  • 接下来,从流中读取 size 对 值及其分数。
  • 该值是字符串编码
  • 下一个字节指定分数编码的长度(无符号整数)。该字节具有3个特殊含义:
    • 253:不是数字。不读取其他字节
    • 254:正无穷大。不读取其他字节
    • 255:负无穷大。不读取其他字节
  • 从流中读取size那么多的字节,将分数表示为ASCII编码的浮点数。使用字符串到浮点转换来获取实际的双精度值。

注意:根据值的具体大小,您可能会丢失一些精度。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
  • 首先,读取有序集合的大小:04 = 4(十进制)
  • 接下来,读取第一个数字,字符串编码。它的长度为 01 = 1(十进制)。读取一个字节:63 = c(ASCII)。
  • 然后读取下一个字节:12 = 18(十进制)。这是ASCII编码分数的长度。
  • 读取18个字节作为ASCII值:4.0199999999999996。如有必要,可以将其解析为双精度值。
  • 读取一个字节:01 = 1,下一个成员的长度。读取该字节:64 = d(ASCII)
  • 再读一个字节:FE = 254(十进制)。这意味着分数是正无穷大。
  • 读取下一个字节:01。同样,下一个成员的长度。读取该字节:61 = a。
  • 读取下一个字节:12 = 18(十进制)。读取接下来的18个字节,并将其解释为ASCII:""3.1899999999999999
  • 读取一个字节:01。同样,下一个成员的长度。读取该字节:65= e。
  • 再读一个字节:FF= 255(十进制)。这意味着分数是负无穷大。
    最终的排序集将是:
{ "e" => "-inf", "a" => "3.189999999999999", "c" => "4.0199999999999996", "d" => "+inf" }

3.6 哈希编码

  • 首先,使用长度编码从流中读取哈希的 size
  • 接下来,使用字符串编码从流中读取下一个 2 * size 的字符串(键和值是交替的字符串)

例:

2 us washington india delhi

表示:

{“us” => “washington”, “india” => “delhi”}

3.7 Zipmap编码

Zipmap是已序列化为字符串的哈希图。本质上,键值对是顺序存储的。在此结构中查找键的复杂度是O(N)。当键值对的数量很少时,使用此结构代替字典。

要解析zipmap,首先要使用字符串编码从流中读取一个字符串。该字符串的内容表示Zipmap编码。

一个字符串中的zipmap的结构如下:

“foo”“bar”“hello”“world”

  • zmlen:1个字节,用于保存Zipmap编码的大小。如果该值大于或等于254,则不使用值。您将必须迭代整个zip映射以找到长度。
  • len:接下来字符串的长度,可以是键或值。该长度以1字节或5字节存储(是的,它与上述的长度编码不同)。如果第一个字节在0到252之间,则这是zipmap的长度。如果第一个字节是253,则接下来的4个字节(无符号整数)表示zipmap的长度。254和255表示此字段是无效的。
  • free:始终为1个字节,表示该值之后的可用字节数。例如,如果key的值为“ America”,并且将其更新为“ USA”,则将有4个可用字节。
  • zmend:总是为255。表示zipmap的结尾。

例:

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 …

  • 首先使用字符串对其进行解码。您会注意到0x18(用十进制表示为224)是字符串的长度。因此,我们将读取接下来的24个字节,即读到FF
  • 现在,我们从02 06…使用Zipmap编码开始解析字符串
  • 02 是hashmap中的条目数。
  • 06 是下一个字符串的长度。由于小于254,因此我们不必读取任何其他字节
  • 我们读取接下来的6个字节,即4d 4b 44 31 47 36得到key“ MKD1G6”
  • 01 是下一个字符串的长度,即为value
  • 00 是可用字节数
  • 我们读取下一个1字节,即0x32。因此,我们获得了value"2"
  • 在这种情况下,可用字节为0,因此我们不会跳过任何内容
  • 05 是下一个字符串的长度,也就是key的长度
  • 我们读取接下来的5个字节59 4e 4e 58 4b,以获取key"YNNXK"
  • 04 是下一个字符串的长度,它是一个值
  • 00 是值后的空闲字节数
  • 我们读取接下来的4个字节,即46 37 54 49获取值"F7TI"
  • 最后,我们遇到FF,它指示zip map的结尾
  • 因此,此zip map表示哈希 {“MKD1G6” => “2”, “YNNXK” => “F7TI”}

3.8 邮编编码

Ziplist是已序列化为字符串的列表。本质上,列表元素的标志和偏移量一起顺序存储,以实现在两个方向上高效遍历列表。

要解析ziplist,首先使用字符串编码从流中读取一个字符串。用此字符串的内容表示ziplist。

一个字符串中的ziplist的结构如下:

  • zlbytes:4字节的无符号整数,表示ziplist的总大小(以字节为单位)。4个字节采用小端格式——最低有效位在前
  • zltail:4字节无符号整数,采用小端格式。它代表了ziplist中尾部(即最后一个)条目的偏移量
  • zllen:这是2字节无符号整数,采用小端格式。它代表此ziplist中的条目数
  • entry:条目代表ziplist中的元素。详情如下
  • zlend:总是255。它代表ziplist的结尾。

ziplist中的每个 entry 都具有以下格式:

  • length-prev-entry:存储前一个条目的长度;如果是第一个条目,则存储0。这样可以轻松地反向浏览列表。该长度以1个字节或5个字节存储。如果第一个字节小于或等于253,则将其视为长度。如果第一个字节为254,则接下来的4个字节用于存储长度。4个字节作为无符号整数读取。

  • special-flag:此标志指示条目是字符串还是整数。它还指示字符串的长度或整数的大小。该标志的各种编码如下所示:

字节数长度含义
00pppppp1个字节长度小于或等于63个字节(6位)的字符串值
01pppppp(qqqqqqqq)2字节长度小于或等于16383字节(14位)的字符串值
10______<4 byte>5字节接下来的4个字节包含一个无符号的int。长度大于或等于16384个字节的字符串值
1100____3个字节整数编码为16位带符号(2个字节)
1101____5字节整数编码为32位带符号(4个字节)
1110____9字节整数编码为64位带符号(8个字节)
1111____4字节整数编码为24位带符号(3个字节)
  • raw-byte:在特殊标志之后,将输入原始字节。字节数先前已确定为特殊标志的一部分。

例:

23 23 00 00 00 1E 00 00 00 04 00 00 E0 FF FF FF FF FF
FF FF 7F 0A D0 FF FF 00 00 06 C0 FC 3F 04 C0 3F 00 FF …

-首先使用字符串编码对其进行解码。23是字符串的长度(十进制为35),因此我们将读取接下来的35个字节,直到FF
-现在,我们从23 00 00 …使用Ziplist编码开始解析字符串

  • 前4个字节23 00 00 00代表此ziplist的总长度(以字节为单位)。请注意:这是小端格式
  • 接下来的4个字节1e 00 00 00表示到尾项的偏移量。1E= 30(十进制),这是一个基于0的偏移量。第0位= 23,第1位= 00,依此类推。因此,最后一个条目开始于04 c0 3f 00 …
  • 接下来的2个字节04 00表示此列表中的条目数,为16位大端整数。04 00 = 4以十进制表示。
  • 从现在开始,我们开始读入entry
  • 00代表上一个条目的长度。0表示这是第一个条目。
  • E0是特殊标志。由于它以位模式1110____开头,因此我们将接下来的8个字节读取为整数。这是列表的第一项。
  • 现在开始第二项
  • 0A是上一个条目的长度。0A= 10以十进制表示。10个字节= 1个字节用于上一个的长度+ 1个字节(特殊标志)+ 8个字节(整数)。
  • D0是特殊标志。由于它以位模式1101____开头,因此我们将接下来的4个字节读取为整数。这是列表的第二项
  • 现在开始第三项
  • 06是上一个条目的长度。6字节= 1字节用于上一个的长度+ 1个字节(特殊标志)+ 4个字节(整数)
  • C0是特殊标志。由于它以位模式1100____开头,因此我们将接下来的2个字节读取为整数。这是列表的第三项
  • 现在开始最后一个条目
  • 04 是上一个条目的长度
  • C0 表示2个字节的数字
  • 我们读取接下来的2个字节,这是我们的第四项
  • 最后,我们遇到FF,这表明我们已经使用了该ziplist中的所有元素。
  • 因此,此ziplist存储值 [0x7fffffffffffffff, 65535, 16380, 63]

3.9 intset 编码

intset是整数的二进制搜索树。二叉树以整数数组实现。当集合的所有元素都是整数时,将使用intset。intset最多支持64位整数。作为一种优化,如果整数可以用更少的字节表示,则整数数组将由16位或32位整数构成。当插入新元素时,会在必要时进行升级。

由于Intset是二叉搜索树,因此该集合中的数字将始终被排序。

一个Intset具有Set的外部接口。

要解析一个Intset,首先使用字符串编码从您的流中读取一个字符串。此字符串的内容表示Intset。

在一个字符串中,Intset具有非常简单的布局:

  • encoding:是一个32位无符号整数。它具有3个可能的值-2、4或8。它表示存储在内容中的每个整数的大小(以字节为单位)。是的,这很浪费-我们可以将相同的信息存储在2个bit中。
  • length-of-contents:是一个32位无符号整数,表示内容数组的长度
  • contents:是 length-of-contents 个字节的数组。它包含整数的二叉树

14 04 00 00 00 03 00 00 00 FC FF 00 00 FD FF 00 00 FE FF 00 00 …

  • 首先使用“字符串编码”对此进行解码。14是字符串的长度,因此我们将读取接下来的20个字节,直到00
  • 现在,我们开始解释从以下位置开始的字符串 04 00 00 …
  • 前4个字节04 00 00 00是编码。由于这等于4,我们知道我们正在处理32位整数
  • 接下来的4个字节03 00 00 00是内容的长度。因此,我们知道我们正在处理3个整数,每个整数4个字节长
  • 从现在开始,我们以4个字节为一组读取,并将其转换为无符号整数
  • 因此,我们的整数看起来像[0x0000FFFC, 0x0000FFFD, 0x0000FFFE]。请注意,整数采用小尾数格式,即最低有效位在前。

3.10 ziplist编码的有序集合

以ziplist编码的有序集合像上面描述的Ziplist一样被排序好。在有序集合中,每个元素后面都跟着其分数。

[‘Manchester City’, 1, ‘Manchester United’, 2, ‘Tottenham’, 3]

如您所见,分数紧随每个元素。

3.11 Ziplist编码的Hashmap

在这种编码下,哈希映射的键值对作为连续条目存储在ziplist中。

注意:这是在RDB版本4中引入的。它不兼容在早期版本中使用的zipmap编码。

{“us” => “washington”, “india” => “delhi”}

在ziplist中存储为:

[“us”, “washington”, “india”, “delhi”]

3.12 Quicklist编码

RDB版本7引入了列表编码的新变体:快速列表Quicklist。

快速列表是一个压缩列表的链接列表。快速列表将小型ziplist的存储效率与链接列表的可扩展性结合在一起,使我们能够创建任意长度的节省空间的列表。

要解析快速列表,首先使用字符串编码从流中读取一个字符串。此字符串的内容表示ziplist。

一个字符串中的快速列表的结构如下:

  • len:这是链表的节点数,使用长度编码表示
  • ziplist:一个字符串,用于包装ziplist,并使用Ziplist编码进行解析

需要从ziplist的所有元素构建一个完整的列表。

例:

01 1F 1F 00 00 00 17 00 00 00 02 00 00 0B 6F 6E 65 2D 65 6C 65 6D 65 6E 74 0D 05 65 6C 65 6D 32 FF

  • 首先读取第一个字节,以获取长度编码中的列表长度。在这种情况下为01,因此列表仅包含一个节点。
  • 以下是一个包含该节点所有元素的压缩列表。
  • 通过阅读Ziplist编码中指定的ziplist继续进行。
  • 读取Blob(字符串编码)
  • 对于此,读取到的长度为 0x1F,因此有31个字节,直到FF
  • 读取这31个字节
  • 读取ziplist的字节数,为32位无符号整数:1F 00 00 00= 31(十进制)。
  • 读取尾部偏移量为32位无符号整数:17 00 00 00= 23(十进制)。它是从零开始的,所以这指向0D 05 65 6C …
  • 以16位无符号整数的形式读取ziplist的长度:02 00= 2(十进制)。
    现在,阅读两个ziplist条目,如下所示:
  • 读取下一个字节:00,因为这是第一项,并且没有前任项。
  • 读取下一个字节:0B= 11(十进制)。由于这是从位模式开始的,因此将00其视为长度。
  • 从流中读取11个字节:6F 6E 65 2D 65 6C 65 6D 65 6E 74= “one-element”(ASCII)
  • 这是第一个要素。
  • 继续第二项。
  • 读取一个字节:0D= 13(十进制)。这是上一项的长度(11的长度+标志+ prev-length)
  • 读取下一个字节:05。这是以下字符串的大小。
  • 读取接下来的5个字节:65 6C 65 6D 32= elem2。这是第二个列表元素。
  • 读取下一个字节:FF。这标志着ziplist的结尾(因为我们读取了之前确定的2个元素)
  • 因此,快速列表存储列表[“one-element”, “elem2”]。

参考文档:

http://rdb.fnordig.de/file_format.html
https://github.com/sripathikrishnan/redis-rdb-tools/blob/master/docs/RDB_File_Format.textile

Redis是一个开源的内存数据存储系统,可以用作数据库、缓存和消息队列等。它支持多种数据结构,如字符串、列表、哈希、集合和有序集合等。 在Redis中,RDB文件是一种持久化的方式,用于将内存中的数据保存到硬盘中。当我们使用RDB文件进行启动时,Redis会将RDB文件加载到内存中,从而恢复原来的数据状态。 RDB文件是通过Redis快照功能生成的,可以通过执行SAVE或BGSAVE命令手动创建RDB文件,也可以根据配置文件中设置的自动快照触发条件周期性地创建RDB文件。 当我们通过RDB文件启动Redis时,首先需要将RDB文件放在Redis的工作目录下。然后,在启动Redis时,可以通过命令行的方式指定RDB文件的路径,例如: redis-server /path/to/redis.conf --dir /path/to/rdb/file 这样,Redis就会加载RDB文件,并将其中的数据恢复到内存中。启动完成后,Redis将可以使用之前保存在RDB文件中的数据。 通过RDB文件启动Redis的优点是恢复速度快,因为RDB文件保存了Redis的快照,加载RDB文件只需要将文件中的数据读取到内存中即可。同时,RDB文件的大小相对较小,占用的磁盘空间较少。 需要注意的是,使用RDB文件进行启动时,最好先备份好最新的RDB文件,以免数据丢失。另外,RDB文件只保存了快照时刻的数据,因此如果在最新RDB文件生成之后有新数据写入,这部分数据是无法恢复的。为了避免数据丢失,还可以将AOF日志功能与RDB文件一起使用,将数据的修改操作追加到AOF日志文件中,确保数据的持久性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值