Redis RDB文件存储格式

Redis存在两种数据文件:RDB和AOF。
AOF文件的结构比较简单,就不做说明。主要是简介RDB数据文件的结构

*.rdb文件是表示Redis瞬间快照的一个二进制文件。根据这个快照文件,可以将Redis恢复到Redis当时的状态。
rdb文件针对读写进行了优化操作。尽可能的会使用LZF压缩算法来减少文件的大小。

在命令行下我们可以通过od -x rdb.rdb | lessod -c rdb.rdb | less命令查看16进制和字符模式下的rdb文件。
注意:使用od -x命令查出来的16进制是逆序的。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
----------------------------# RDB文件是二进制的,所以并不存在回车换行来分隔一行一行.
52 45 44 49 53 # 以字符串 "REDIS" 开头
30 30 30 36 # RDB 的版本号,大端存储,比如左边这个表示版本号为0006
----------------------------
FE 00 # FE = FE表示数据库编号,Redis支持多个库,以数字编号,这里00表示第0个数据库
----------------------------# Key-Value 对存储开始了
FD $length-encoding # FD 表示过期时间,过期时间是用 length encoding 编码存储的,后面会讲到
$value-type # 1 个字节用于表示value的类型,比如set,hash,list,zset等
$string-encoded-key # Key 值,通过string encoding 编码,同样后面会讲到
$encoded-value # Value值,根据不同的Value类型采用不同的编码方式
----------------------------
FC $length-encoding # FC 表示毫秒级的过期时间,后面的具体时间用length encoding编码存储
$value-type # 同上,也是一个字节的value类型
$string-encoded-key # 同样是以 string encoding 编码的 Key值
$encoded-value # 同样是以对应的数据类型编码的 Value 值
----------------------------
$value-type # 下面是没有过期时间设置的 Key-Value对,为防止冲突,数据类型不会以 FD, FC, FE, FF 开头
$string-encoded-key
$encoded-value
----------------------------
FE $length-encoding # 下一个库开始,库的编号用 length encoding 编码
----------------------------
... # 继续存储这个数据库的 Key-Value 对
FF ## FF:RDB文件结束的标志
8 byte checksum ## 8位校验码




------------------------------------
od -x rdb.rdb | head -n 10
0000000 4552 4944 3053 3030 fe36 fc1e ae47 2160
0000020 016e 0000 2800 6c65 7361 6974 5f63 3036
0000040 3930 3562 6432 3139 3962 3233 3134 3138
0000060 6338 3031 3338 6166 6233 3163 6264 5d4e
0000100 8b1f 0008 0000 0000 ff00 9bcc 535b 595a
0000120 c79b 66fb e7ae a42b 1db8 7585 7bde dea7
0000140 a779 eb62 7264 3500 e931 2a1c 2165 116c
0000160 6082 1838 27b5 1855 8b4d fb67 d18d 6344
0000200 3a62 6231 51b7 6dd3 6247 bb50 9aac b9ab
0000220 8f9c d610 7366 af95 b530 cd01 d906 8104

-------------------------------------
od -c rdb.rdb | head -n 10
0000000 R E D I S 0 0 0 6 376 036 374 G 256 ` !
0000020 n 001 \0 \0 \0 ( e l a s t i c _ 6 0
0000040 0 9 b 5 2 d 9 1 b 9 3 2 4 1 8 1
0000060 8 c 1 0 8 3 f a 3 b c 1 d b N ]
0000100 037 213 \b \0 \0 \0 \0 \0 \0 377 314 233 [ S Z Y
0000120 233 307 373 f 256 347 + 244 270 035 205 u 336 { 247 336
0000140 y 247 b 353 d r \0 5 1 351 034 * e ! l 021
0000160 202 ` 8 030 265 ' U 030 M 213 g 373 215 321 D c
0000200 b : 1 b 267 Q 323 m G b P 273 254 232 253 271
0000220 234 217 020 326 f s 225 257 0 265 001 315 006 331 004 201

魔数

rdb文件固定以魔术字符串”REDIS”开头,表示这个rdb文件时Redis数据文件。
52 45 44 49 53 # "REDIS"

版本号

接下来固定4个字节保存rdb格式的的版本号。
30 30 30 36 # Version = 6

数据库编号

一个字节0xfe表示开始选择数据库;在这个字节之后,一个可变长度字段表示数据库编号。具体部分参考长度编码部分
1e表示十进制的30,代表是30数据库

键值对

在选择数据库后,就是具体的一些键值对记录。每个键值对包含4个部分

名称 大小 说明
RDB_OPCODE_EXPIRETIME_MS 1byte 0xfc/0xfd(252/253),说明是带过期时间的键值对
ms 8bytes 时间戳
TYPE 1byte 键值对类型
key —-
value —-

过期标记和过期时间戳

一个字节。0xFD表示过期时间戳以秒为单位,0xFC表示过期时间戳以毫秒为单位。
如果设置了过期时间,那么接下来的8个字节就表示具体的过期时间。
在导入RDB文件的时候,会将已经过期的key进行丢弃。

如果没有设置过期时间,则没有这两个字段。

key类型

一个字节,表示保存键值对value的具体编码。

  • 0表示是一个简单字符串
  • 当值为9-13时,该值被包装到字符串中,读取字符串后,会对其进行进一步解析。
  • 当值为1-4时,该值是一个字符串序列,此字符串序列用于构造列表、集合、集合或哈希表等复杂数据类型。

键值对

key固定为字符串编码保存。
value取决于编码,不同的编码有不同的保存格式。
后面会有详细介绍。

长度编码

长度编码用于存储下一个对象的长度,长度编码是一种可变长字节编码,目的在于使用更少的字节来表示内容。
从数据流中取出一个字节,前面两个bit可能存在以下几种情况:

  • 00:接下来6位表示长度
  • 01:取出下一个字节,组合的14位表示长度
  • 10:剩余6位舍弃,取出4个字节表示长度
  • 11:表示下一个对象以特殊格式编码。其余6位表示格式。

63的数字只需要一个字节进行存储
64 – 16383 的数字只需要两个字节进行存储
16383 - 2^32 -1 的数字只需要用5个字节(1个字节的标识加4个字节的值)进行存储

字符串编码

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

在RDB文件中有三种类型的字符串:

  1. 简单字符串
  2. 8、16、32位的整数
  3. 一个经过LZF压缩的支付串

简单字符串

简单字符串编码非常简单,就是 字符串长度+具体的字符串

整数字符串

在长度编码为11的时候,读取后续的6位。如果后续的6位为:

  • 0表示跟随的是8位整数
  • 1表示跟随的是16位整数
  • 2表示跟随的是32位整数

压缩字符串


在长度编码为11的时候,读取后续的6位。如果后续的6位为3表示后面跟随着的是压缩字符串。具体步骤为下:

  1. 使用长度编码方式从c_len中读取出压缩的长度
  2. 使用长度编码方式从o_len中读取出压缩前的长度
  3. 读取出压缩后的字符串
  4. 使用LZF算法解压缩

List编码和Set编码

一个Redis list表示为一序列字符串

  1. 首先,从流中读取list的大小,按照长度编码的方式读取出size大小值。
  2. 按照字符串编码方式从流中读取出size个字符串
  3. 使用这些字符串重新构建list

Hash编码

  1. 从流中按照长度编码的方式读取出hash表的大小size
  2. 从流中按照字符串编码方式读取出2*size个字符串对象
  3. 键值对交替出现

Ziplist编码

一个Ziplist编码本质上是一个list类型的字符串,借助于标记(flag)和偏移量(offset)来达到双向遍历。
为解析一个ziplist,首先从流中按照字符串编码读取一个字符串,这个字符串就是ziplist的封装。
结构如下:

  1. zlbytes :这是一个 4 字节无符号整数,表示 ziplist 的总字节数。这 4 字节是 little endian 格式--最先出现的是最低有效位组
  2. zltail:这是一个 4 字节无符号整数,little endian 格式。它表示到 ziplist 的尾条目(tail entry)的偏移。
  3. zllen:这是一个 2 字节无符号整数,little endian 格式。它表示 ziplist 的条目的数量
  4. entry:一个条目表示 ziplist 的元素。细节在下面
  5. zlend:总是等于 255。它表示 ziplist 的结束

ziplist的每个entry都是以下这样的格式: <length-prev-entry><special-flag><raw-bytes-of-entry>
具体参考Redis中ziplist的实现方式。

CRC校验和

从Redis5开始启动参数控制是否将8字节的校验和添加到文件末尾,可以通过修改参数关闭该功能。
当禁用校验和的时候,此字段为0

https://github.com/sripathikrishnan/redis-rdb-tools/blob/master/docs/RDB_File_Format.textile
https://github.com/sripathikrishnan/redis-rdb-tools/blob/master/docs/RDB_Version_History.textile
https://github.com/wen866595/open-doc/blob/master/redis-doc/Redis-RDB-Dump-File-Format-cn.md
https://blog.csdn.net/guiqulaxi920/article/details/51177307
http://ascii.911cha.com/