[转帖]Redis持久化文件RDB的格式解析

redis,持久,文件,rdb,格式,解析 · 浏览次数 : 0

小编点评

Sure, here is the generated content: **Sorted Set Size:** 4 **First Member:** * String: 123456 * Length: 5 **Following Members:** * String: "e" (negative infinity) * String: "a" (positive infinity) * String: "3.1899999999999996" * String: "4.0199999999999996" * String: "+inf" **Hash Code:** 123456 **Zip Code:** * String: "1<zmlen><len>\"foo\"<len><free>\"bar\"<len>\"hello\"<len><free>\"world\"<zmend> zmlen: 1 byte * String: "2<zmlen><len>\"foo\"<len><free>\"bar\"<len>\"hello\"<len><free>\"world\"<zmend> zmlen: 1 byte * String: "4d 4b 44 31 47 36 01 00 32 05 59 4E 4E 58 4b 04 00 46 37 54 49 FF" **Note:** The string after the first member is a ZipCode, containing information about the map's size and content.

正文

http://www.innereye.cn/2017/01/17/redis/Redis_RDB_File_Format_20170117/

 

本文可转载演绎,但需要注明原作者和本文链接。

 发表于 2017-01-17

文章目录
  1. 1. Redis RDB文件格式
  2. 2. 解析RBD文件的高级算法
  3. 3. 文件魔数 Magic Number
  4. 4. RDB版本号
  5. 5. 操作码
  6. 6. 数据库选择器
  7. 7. Resizedb信息
  8. 8. 辅助字段
  9. 9. KV键值对
    1. 9.1. 失效时间戳
    2. 9.2. 值类型
    3. 9.3. 键-Key
    4. 9.4. 值-Value
  10. 10. 编码
    1. 10.1. 长度编码
    2. 10.2. String编码
      1. 10.2.1. Length Prefixed String
      2. 10.2.2. Integer As String
      3. 10.2.3. Compressed Strings
    3. 10.3. List编码
    4. 10.4. Set编码
    5. 10.5. SortedSet编码
    6. 10.6. Hash编码
    7. 10.7. ZipMap编码

Redis RDB file format.
介绍第7版的RDB

Redis RDB文件格式

Redis的RDB文件是对内存存储的一种表示。这个二进制文件足以完全恢复Redis当时的运行状态。
RDB文件格式针对快速读写进行了优化。LZF压缩被用于减小文件大小。
通常,对象的长度会作为该条记录的前缀,所以在读取对象前,你已经精确地知道了需要分配多少内存。
优化文件的快速读写,意味着数据在磁盘中的格式,尽可能的和内存中展示的一样。
这就是RDB文件采用的方法。
因此,你可以在不了解Redis内存数据结构的前提下,解析RDB文件。

解析RBD文件的高级算法

整体看,RDB的文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
----------------------------#
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表示的文件校验和

文件魔数 Magic Number

RDB文件以字符串“REDIS”作为开始。
这是一个快速完整性校验,用于判断是否在处理一个RDB文件。

52 45 44 49 53 # “REDIS”

RDB版本号

接下来的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用于标识数据库选择器部分的开始。
在该字节后,一个变长的字段表示数据库的索引值。
了解如何读取该数据库索引值,请参照长度编码

Resizedb信息

这个操作码是RDB版本7引入的。
这部分包含两个编码后的值,用于加速RDB的加载,避免在加载过程中额外的调整hash空间(resize)和rehash操作。
在操作码之后的两个值是:

  1. 数据库的哈希表大小。
  2. 失效哈希表的大小。

辅助字段

这个操作码是RDB版本7引入的。
操作码后是两个Redis字符串,表示设置的KV键值对。
未知的字段会被解析器忽略。

目前实现的配置有:

  • redis-ver:Redis的版本号
  • redis-bits:输出该RDB文件的操作系统位架构,32或者64
  • ctime:该RDB文件的创建时间
  • used-mem:输出该RDB文件的Redis使用的内存大小

KV键值对

在数据库选择器信息后,这个文件包含了一系列的KV键值对序列。
每个KV键值对由4部分组成:

  1. Expiry Time:失效时间戳。这个字段是可选的。
  2. Value Type:一个字节的标识符,指明Value的类型。
  3. Key:使用Redis的String Encoding。参照String编码部分。
  4. Value:编码方式根据Value Type字段决定。参照编码部分。

失效时间戳

该部分由一个字节标识开始,该表示可以是以下两种之一:

  • 0xFD:该标识表示,失效时间以秒为单位。接下来的4个字节组成一个无符号的整型,表示Unix时间戳。
  • 0xFC:该标识表示,失效时间以毫秒为单位。接下来的8个字节组成一个无符号的长整型,表示Unix时间戳。

在导入过程中,如果key失效了,必须被忽略掉,即不加载。

值类型

一个字节来表示value使用的编码方式。

键-Key

Key被当作一个Redis字符串进行编码。参照String编码部分,了解key是如何编码的。

值-Value

值的编码方式根据Value Type字段决定。参照编码部分。

编码

长度编码

长度编码用于存放,在流中下一个对象的长度。
长度编码是一个变长的字节编码,目的是尽可能的使用少量的字节。
长度编码的工作原理:从流中读取一个字节,比较两个最高有效比特(bit)位:

比特如何解析
00 接下来的6个bit表示长度
01 接下来的6个bit,加上再读取一个字节(即8bit),组成的14 bit表示长度。
10 忽略该字节剩下的6个bit。再从数据流中读取4个字节,表示长度。
11 接下来读取的对象使用了特殊编码。该字节剩余的6个bit表示编码格式。可能存放整数型或者字符串,参照String编码

结论:

  • 整数0-63(2^6-1)可以存放在1个字节中。
  • 整数64-16383(2^14-1)可以存放在2个字节中。
  • 整数16383-(2^32-1)可以存放在4个字节中。

String编码

Redis字符串是二进制安全-这意味着你可以存放任意数据。
字符串没有任何的特殊字符串作为结尾标记。
最好将Redis的字符串看作一个字节数组。

Redis有三种String类型:

Length Prefixed String

长度作为前缀的字符串,格式非常简单,首先,字符串的字节长度使用长度编码
之后存放的就是该字符串的原始字节数组。

Integer As String

首先按照长度编码读取,开始的两个bit是11(二进制)
在这种情况下,剩下的6个字节值的如果情况:

  • 0,表示接下来是一个8bit的整数。
  • 1,表示接下来是一个16bit的整数。
  • 2,表示接下来是一个32bit的整数。
Compressed Strings

首先阅读长度编码章节,特别是开始的两个bit是11(二进制)的时候。
在这种情况下,读取剩下的6个bit值。如果这6bit的值是3,则表示接下来是一个压缩的字符串。

压缩字符串的读取方式如下:

  • 从数据流中按照长度编码读取压缩字符串的长度clen
  • 从数据流中按照长度编码读取未压缩字符串的长度。
  • 从数据流中读取接下来clen长的byte。
  • 最后,这些byte使用LZF算法解压缩。

List编码

一个Redis的List使用一系列字符串表示。

  • 首先,列表的大小size,使用长度编码从数据流中读取。
  • 接着,size个字符串使用String编码方式读取。
  • 然后这个列表使用这些字符串重新创建。

Set编码

set的编码方式和list一模一样。

SortedSet编码

  1. 首先,使用长度编码从数据流中读取Sorted Set的大小size
  2. 然后,从数据流中读取size个值(value)、分数(score)组。
  3. 值使用String编码
  4. 下一个byte执行压缩后分数(score)的长度(一个无符号整数)。这个byte有3个值具备特殊含义。
    • 253:不是一个整数,不需要读取额外的byte。
    • 254:正无穷大,不需要读取额外的byte。
    • 255:负无穷下,不需要读取额外的byte。
  5. 从流中读取相应数量的byte,分值使用ASCII编码表示的float,使用String转换float的方式获取相应的浮点值。

注意:由于分值的精度可能失真。Redis使用双精度存放该分值。

注意:不能确保读取到的组合是有序的。

示例:

1
2
3
4
5
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

 

  1. 首先读取sorted set的大小:04 = 4 (十进制)
  2. 然后,读取第一个成员,使用String编码。它的长度01 = 1 (十进制)。读取一个byte:63 = c (十进制)
  3. 然后读取下一个byte:12 = 18 (十进制)。这是接下来score使用ASCII编码的字符串长度。
  4. 读取18个bytes,ASCII值:4.0199999999999996。如果需要,可以将其转换为double值。
  5. 读取一个byte:01 = 1, 即下一个成员的长度。读取一个byte:64 = d(ASCII)。
  6. 读取一个byte:FE = 254(十进制)。这表示该分值为正无穷大。
  7. 读取一个byte:01 = 1, 即下一个成员的长度。读取一个byte:61 = a(ASCII)。
  8. 读取一个byte:12 = 18 (十进制)。读取18个byte,将它们转换成ASCII码:3.1899999999999999
  9. 读取一个byte:01 = 1, 即下一个成员的长度。读取一个byte:65 = e(ASCII)。
  10. 读取一个byte:FF = 255(十进制)。这表示该分值为负无穷小。

最后sorted set为:

1
2
3
4
5
6
{
"e" => "-inf",
"a" => "3.189999999999999",
"c" => "4.0199999999999996",
"d" => "+inf"
}

 

Hash编码

  1. 首先,使用长度编码从数据流中读取Hash的大小size
  2. 然后从流中使用String编码读取2 * size个字符串。

示例:

1
2 us washington india delhi

表示:

1
2
3
4
{
"us" => "washington",
"india" => "delhi"
}

 

ZipMap编码

一个ZipMap就是一个HashMap被序列化成的一个字符串。
实质上,键值对是被顺序存储的。
在该结构中查找一个key的算法时间复杂度是O(N)
当键值对的数量较少时,使用该结构而不是字典结构。
解析一个ZipMap,首先使用String编码从流中读取一个字符串。这个字符串就表示该ZipMap。

一个ZipMap使用字符串的表达形式如下:

1
<zmlen><len>"foo"<len><free>"bar"<len>"hello"<len><free>"world"<zmend>

 

  • zmlen:一个byte,表示该ZipMap的大小。如果该值大于或者等于254,则表示该值不可用。此时需要迭代整个ZipMap,算出该ZipMap的大小。
  • len:下一个字符串的长度,该字符串可能是key或者是value。这个字符串的长度可能被1个byte或者5个byte表示(和上面长度编码不同)。如果第一个byte的值是0到252,那么这就是该ZipMap的长度,如果该byte值是253,则接下来的4个byte组成一个无符号整数,表示ZipMap的长度。对于该值254、255是非法的。
  • free:这个字段总占用1个byte,并指明了该值后空闲byte的数量。例如,如果一个key的value是“America”,并且更新为“USA”,就会空闲4个byte。
  • zend:该值总是255,表示ZipMap的结尾。

示例:

1
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 ..

 

  • 首先使用String编码方式,反解码这个字符串。你会发现该字符串的长度是0x18(十进制值是24)。实际上,我们需要读取接下来的24个byte,即一直到FF
  • 现在,我们使用ZipMap编码解析该字符串02 06 …
  • 02是HashMap的条目数。
  • 06是下一个字符串的长度,由于这个值小于254,我们不必读取额外的字节。
  • 我们读取接下来的6个byte,即4d 4b 44 31 47 36,得到key:MKD1G6
  • 01是接下来字符串的长度,即value的长度。
  • 00表示空闲字符串的个数,即0。
  • 我们读取接下来的1个byte,即32,得到value:2
  • 在本条记录中,由于空闲字符串个数是0,所以不需要跳过任意byte。
  • 05是下一个字符串的长度,即key。
  • 我们读取接下来的5个byte,即59 4e 4e 58 4b,得到key:YNNXK
  • 04是接下来字符串的长度,即value的长度。
  • 00表示空闲字符串的个数,即0。
  • 我们读取接下来的4个byte,即46 37 54 49,得到value:F7TI

这样,我们得到ZipMap如下:

1
2
3
4
{
"MKD1G6" => "2",
"YNNXK" => "F7TI"
}

 

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1tl6lbu6kcce

与[转帖]Redis持久化文件RDB的格式解析相似的内容:

[转帖]Redis持久化文件RDB的格式解析

http://www.innereye.cn/2017/01/17/redis/Redis_RDB_File_Format_20170117/ 本文可转载演绎,但需要注明原作者和本文链接。 By BaiFan 发表于 2017-01-17 文章目录 1. Redis RDB文件格式 2. 解析RBD

[转帖]Redis持久化-RDB和AOF

持久化的功能: Redis是内存数据库, 数据都是存储在内存中, 为了避免进程退出导致数据的永久丢失, 需要定期将Redis中的数据以某种形式(数据或命令) 从内存保存到硬盘。 当下次Redis重启时, 利用持久化文件实现数据恢复。 除此之外, 为了进行灾难备份, 可以将持久化文件拷贝到一个远程位置

[转帖]Redis数据库的备份与恢复

Redis备份 当程序有较多的任务在执行时,我们可以redis-cli模式下执行 bgsave,进入后台进行备份. Snapshot 当然我们可以直接运行save命令.会写入dump.rdb文件。 配置说明: save 900 1 #当900秒之后有一个key变化进行持久化保存 save 300 1

[转帖]redis 持久化方式 - aof 和 rdb 区别

https://wenfh2020.com/2020/04/01/redis-persistence-diff/ aof 和 rdb 是 redis 持久化的两种方式。我们看看它们的特点和具体应用场景区别。 1. 持久化特点 1.1. aof 1.2. rdb 2. 使用场景区别 3. 持久化详细文

[转帖]Redis进阶实践之十 Redis哨兵集群模式

Redis进阶实践之十 Redis哨兵集群模式 一、引言 上一篇文章我们详细的讲解了Redis的主从集群模式,其实这个集群模式配置很简单,只需要在Slave的节点上进行配置,Master主节点的配置不需要做任何更改,但是有一点,Master和Slave两个节点的持久化配置尽量保持一致,否则会有奇怪的

[转帖]Redis 持久化原理和实现

https://juejin.cn/post/6877763937513766919 Redis 所有的数据和状态存储在内存中,为了避免进程退出而导致数据丢失,需要将数据和状态保存到硬盘上。 为了达到这一目的,通常有两种实现方式: 将 Redis 当作一个状态机,记录每一次的对 Redis 的操作,

[转帖]深入理解Redis的持久化

https://www.cnblogs.com/ivictor/p/9749465.html RDB RDB是将当前数据生成快照保存到硬盘上。 RDB的工作流程: 1. 执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。

[转帖]Redis 运维实战 第05期:RDB 持久化

https://cloud.tencent.com/developer/article/1986826 前面一节,我们聊了 AOF,AOF 有个不足点就是:进行数据恢复时,需要逐一把日志都执行一遍,非常耗时间。 Redis 还有另外一种持久化方法:内存快照。指内存中的数据在某一时刻的状态记录,这个快

[转帖]Redis 运维实战 第04期:AOF 持久化

Redis 运维实战 第04期:AOF 持久化 https://cloud.tencent.com/developer/article/1986824 Redis 有两种持久化方式:AOF 和 RDB。本节就先来聊聊 AOF。 AOF(Append Only File) 日志是写后日志,Redis

[转帖]Redis进阶(发布订阅,PipeLine,持久化,内存淘汰)

目录 1、发布订阅 1.1 什么是发布订阅 1.2 客户端实例演示 1.3 Java API演示 1.4 Redis发布订阅和rabbitmq的区别 2、批量操作 2.1 普通模式与 PipeLine 模式 2.2 适用场景 2.3 源码解析 2.4 Pipelining的局限性 2.5 事务与 L