Redis 数据库文件浅析

156 阅读13分钟

本篇文章主要针对 Redis 的两种持久化机制:AOF(Append-Only File)和 RDB(Redis DataBase)进行初步解析。本文侧重于 Redis 6.x 版本,不同版本可能有细微差异。

AOF 文件结构

本章节主要介绍AOF文件结构并通过一个简单的demo展示了AOF的具体内容。

AOF 简述:什么是 AOF? AOF 是 Redis 的一种持久化方案,用于记录服务器接收到的每一条写操作。这些操作在服务器启动时会被重新执行,从而重构原始的数据集。与 RDB 持久化方案不同,AOF 以日志文件的形式持续地保存数据更改。这种方式提供了更高的数据持久性。

主要特点:

1.数据恢复: AOF 文件中的命令可以在 Redis 启动时回放,以达到恢复数据的目的。

2.日志格式: 命令以 Redis 协议本身的相同格式进行记录。

3.文件增长: 由于 AOF 会以追加的方式记录每一条 Redis 的写命令,因此随着写命令的增多,AOF 文件也会逐渐增大。

4.日志重写(AOFRW): 由于 AOF 会记录每一个 Redis 命令,因此 AOF 文件会随时间不断增大。为了解决这个问题,Redis 提供了 AOF 文件重写功能,以“瘦身” AOF 文件。这通过创建一个新的 AOF 文件来实现,新旧两个 AOF 文件保存的数据是相同的,但新的 AOF 文件没有冗余命令。

工作机制

1.命令追加(Append): 当 AOF 持久化功能处于开启状态时,服务器会在执行完一个写命令后,将该命令以协议格式追加到一个名为 aof_buf 的缓冲区。

2.文件写入(WRITE)与同步(SYNC): aof_buf 缓冲区的内容会被写入到 AOF 文件中。Redis 提供了三种同步策略:

  • appendfsync always: 将 aof_buf 缓冲区中的所有内容写入并同步到 AOF 文件。
  • appendfsync everysec: 将 aof_buf 缓冲区中的所有内容写入 AOF 文件,如果距离上次同步的时间超过1秒,则进行两次同步操作。
  • appendfsync no: 将 aof_buf 缓冲区中的所有内容写入 AOF 文件,但不进行同步,同步时间由操作系统决定。

在 Redis 的源代码中,AOF 持久化主要涉及以下几个关键部分:

  • aof.c: 包含 AOF 持久化的主要逻辑。
  • aof_rewrite(): 这是 AOF 重写(AOFRW)的核心函数。
  • flushAppendOnlyFile(): 负责将缓冲区的数据写入到 AOF 文件。

AOF 文件结构:详细解析 AOF 文件的基本构成

AOF(Append Only File)文件是一个纯文本文件,用于记录 Redis 数据库中的所有写操作。这些写操作以 Redis 命令的形式存在,按照它们被执行的顺序进行记录。

image.png AOF 文件结构较为简单,上图展示了AOF文件结构,键值对以文本指令的形式存储在文件中。

AOF 文件读取与解析

当 Redis 服务器启动时,它会读取 AOF 文件,并按照文件中的命令顺序重新执行这些命令,以此来恢复数据状态。读取和解析 AOF 文件的过程大致如下:

  1. 打开 AOF 文件。
  2. 逐行读取文件内容。
  3. 解析每一行为 Redis 命令和参数。
  4. 执行解析出的命令。

AOF 其他特性:Redis 7.0、优化

**
**

在 Redis 7.0 中,AOF(Append-Only File)持久化有了一些重要的更新和优化。根据 Redis 7.0-rc1 的发布说明,新版本引入了一种名为 "AOF log shipping" 的特性。这个特性允许 Redis 实例将 AOF 日志文件发送到远程服务器,从而提供了一种新的数据备份和恢复机制。

其次,根据一个 GitHub issue 的评论,Redis 7.0 还优化了 AOF 重写的性能。这主要是通过减少 AOF 重写期间的内存使用量来实现的,从而提高了大数据集下的性能。

AOF 持久化虽然提供了高度的数据安全性,但也可能影响 Redis 的性能。因此,了解如何优化 AOF 的性能是非常重要的。关于 AOF 性能优化的大致方向有以下几种:

1.使用合适的同步策略(fsync): Redis 提供了多种 fsync 策略,包括 always、everysec 和 no。选择合适的 fsync 策略可以显著影响 AOF 的性能。

2.定期重写 AOF 文件: AOF 文件会随着时间的推移而增长,这可能会影响性能。使用 BGREWRITEAOF 命令可以在后台重写 AOF 文件,从而优化其性能。

3.启用 AOF 与 RDB 的混合模式: 通过设置 aof-use-rdb-preamble 为 yes,可以使 AOF 文件包含一个 RDB 前缀,这样可以加速数据恢复速度并减小 AOF 文件的大小。

4.监控和调优: 使用监控工具来跟踪 AOF 的性能指标,如延迟、磁盘 I/O 等,并据此进行调优。

**
**

AOF 实践

**
**

为了在 Redis 中启用 AOF 持久化功能,可以更新 redis.conf 文件:

appendonly yes

appendonly yes 或者在 Redis CLI 中运行以下命令:

CONFIG SET appendonly yes

在我的机器上,执行了如下命令:


127.0.0.1:6379> config set appendonly yesOK127.0.0.1:6379> set a aOK127.0.0.1:6379> set b bOK

在这个 redis 中我插入了两个数据,对应的,我们可以在 AOF 文件中看到两次set的操作记录。

root@vagrant:~/redis/src/appendonlydir# cat appendonly.aof*2$6SELECT$10*3$3set$1a$1a*3$3set$1b$1b

RDB(Redis DataBase Backup)文件结构

本章节主要介绍RDB文件结构并借助 redis-rdb-cli工具解析了 RDB 的具体内容。

RDB 简述:什么是 RDB?

RDB 是 Redis 的另一种持久化方案,用于在特定的时间点对数据集进行快照存储。与 AOF(Append-Only File)持久化方案不同,RDB 生成的是一个二进制文件,该文件包含某一时刻 Redis 数据库中所有数据的副本。

**
**

主要特点

1.数据恢复: 通过加载 RDB 文件,Redis 可以在启动时快速恢复数据。

2.二进制格式: RDB 文件是一个经过压缩的二进制文件,因此占用的磁盘空间相对较小。

3.性能优化: 由于 RDB 是通过子进程进行快照生成的,因此对主进程的性能影响较小。

4.定时快照: 用户可以配置 Redis 在多少秒内有多少次写操作时进行快照,以满足不同的数据持久性需求。

工作机制

1.快照生成: 当满足用户配置的条件时,Redis 会 fork 出一个子进程来生成 RDB 文件。

2.数据加载: 在 Redis 启动或执行 restore 命令时,RDB 文件会被加载到内存中。

3.自动与手动触发: 除了自动触发 RDB 快照外,用户还可以手动通过 SAVE 或 BGSAVE 命令来生成 RDB 文件。

在 Redis 的源代码中,RDB 持久化主要涉及以下几个关键部分:

  • rdb.c: 包含 RDB 持久化的主要逻辑。
  • rdbSave(): 这是 RDB 快照生成的核心函数。
  • rdbLoad(): 负责从 RDB 文件中加载数据到 Redis 数据库。

RDB 文件结构:详细解析

image.png Magic Number (REDIS)

  • 长度:5 字节
  • 作用:Magic Number 是用于标识一个文件是否为 RDB 格式的文件。这是 RDB 文件的第一个字段,用于快速识别文件类型。
  • 示例:REDISMagic Number 是 RDB 文件的“签名”,当 Redis 或其他工具尝试读取一个 RDB 文件时,会首先检查这 5 个字节。如果这些字节不是 "REDIS",则文件会被认为是无效的。

RDB Version Number

**
**

  • 长度:4 字节
  • 作用:这个字段表示 RDB 文件的版本号,用于处理向后兼容性。
  • 示例:0007不同版本的 Redis 可能会有不同的 RDB 格式。通过这个版本号,Redis 可以知道如何正确地解析文件。

Database Blocks

  • 包含多个数据库(通常是 16 个),每个数据库都有自己的键值对。
  • 每个数据库块开始于一个特定的标识符(例如,FE 表示一个新的数据库)。Database Blocks 是 RDB 文件中的主体部分,包含了所有数据库的数据。每个数据库都有一个或多个键值对,这些键值对以特定的编码(例如,字符串、列表、集合等)存储。

Key-Value Pairs

  • 包含键和值的类型、长度和实际数据。
  • 类型通常由一个字节表示,例如,string 类型可能由 00 表示。这是 RDB 文件中最核心的部分,它描述了实际的数据。每个键值对都有一个类型标识符和相应的数据。

End Of File (EOF)

  • 一个特殊的标识符,表示 RDB 文件的结束。
  • 通常是 FF。这是一个特殊的标识符,用于表示 RDB 文件的结束。当解析器读到这个标识符时,它会知道所有的数据库块和键值对都已经读取完毕。

Checksum

  • 长度:8 字节
  • 作用:用于验证 RDB 文件的完整性。Checksum 是所有前面数据的 CRC64 校验和。这用于在读取文件时检测任何潜在的错误或数据损坏。

下图图是一个实际的 RDB FILE 二进制文件的 16 进制的表示形式。根据上述的RDB文件的整体结构我们可以得到如下的解析结果。

image.png

image.png

String 类型数据存储结构

存储方式

在 RDB 文件中,String 是最常见的数据结构,有三种主要的存储方式:

  1. 整数存储:如果字符串是一个 8、16 或 32 位的整数,它会被直接存储为一个整数。这种方式非常高效,因为整数通常会占用更少的存储空间,并且在读取时也更快。

  2. LZF 压缩:如果字符串的长度大于 20 个字符,并且 LZF 压缩被启用,那么在存储之前,字符串会被压缩。这种方式在处理大量长字符串时非常有用,可以显著减少存储空间。

  3. 字节数组存储:在其他所有情况下,字符串会被存储为一个简单的字节数组。这是最直接的存储方式,不涉及任何额外的处理或转换。

长度编码字段(Length Encoding Field)

**
**

长度编码字段是一个非常重要的组成部分,用于指示如何存储接下来的字符串结构。这个字段通常是一个字节长,其中最重要的两位用于指示字符串的存储方式。

  • 字节数组存储: 如果最高的两位是 00、01 或 10,字符串会被存储为一个字节数组。这里,接下来的位数(可能是 6 位、14 位或 32 位)用于表示字节数组的实际长度。

  • 整数存储: 如果最高的两位是 11,并且接下来的 6 位中的值是 0、1 或 2,那么这表示字符串被存储为一个 8 位、16 位或 32 位的整数。

  • LZF 压缩存储: 如果最高的两位是 11,并且接下来的 6 位中的值是 4,这表示字符串被 LZF 压缩后存储。这里,还会有额外的字段来表示压缩前和压缩后的长度。

下面两个表格是string 二进制存储文件的16进制表示以及字段解析后的含义。

image.png

image.png RDB 其他特性:压缩优化、其他配置 压缩优化

**
**

在 Redis 中,RDB 文件默认使用基于 LZF 的压缩算法进行优化。这种压缩算法非常高效,能够在不牺牲读取速度的前提下显著减小 RDB 文件的大小。这对于需要定期备份或迁移数据的场景尤为重要。

在 redis.conf 配置文件中,你可以通过以下选项来启用或禁用 LZF 压缩:

rdbcompression yes # 启用 LZF 压缩rdbcompression no   # 禁用 LZF 压缩

启用压缩会稍微增加 CPU 的使用率,因为需要进行压缩和解压缩操作。但通常这个开销是可以接受的,特别是考虑到它带来的存储优势。

RDB 文件名和路径:

默认情况下,RDB 文件保存在 Redis 服务器的当前目录下,文件名为 dump.rdb。你可以通过 dbfilename 和 dir 选项来自定义文件名和路径。

dbfilename mydump.rdb # 自定义 RDB 文件名dir /path/to/redis/data # 自定义 RDB 文件路径

自动保存配置:

通过 save 配置选项,你可以设置在多少秒内,如果有多少次写操作,则自动创建 RDB 文件。

save "900 1" # 在 900 秒内如果有 1 次写操作则保存save "300 10" # 在 300 秒内如果有 10 次写操作则保存

数据完整性校验:

你可以通过 rdbchecksum 选项来启用或禁用数据完整性校验。

rdbchecksum yes # 启用数据完整性校验rdbchecksum no   # 禁用数据完整性校验

RDB 文件解析工具:实用工具介绍

解析 RDB 文件可以采用 leonchen83/redis-rdb-cli 这个开源的工具,该工具可以用于解析Redis dump.rdb文件,分析内存,并将数据导出到JSON。

在我的机器上,执行了如下命令:

127.0.0.1:6379> set a aOK127.0.0.1:6379> set b bOK127.0.0.1:6379> lpush mylist c c(integer) 2127.0.0.1:6379> saveOK

在这个 redis 中我插入了两个数据,并创建了一个list对象push了两个数据,接下来我们可以采用 rct 工具进行解析,从rdb文件中看到三个数据对象的值。

root@vagrant:~/redis/src/redis-rdb-cli/bin# ./rct -f json -s /root/redis/src/dump.rdb -o out.json
root@vagrant:~/redis/src/redis-rdb-cli/bin# cat out.json | jq .[{   "key": "mylist",   "value": [     "c",     "c"  ],   "db": 0,   "type": "list"},{   "key": "a",   "value": "a",   "db": 0,   "type": "string"},{   "key": "b",   "value": "b",   "db": 0,   "type": "string"}]

总结而言,Redis的两种主要持久化机制,AOF(Append-Only File)和 RDB(Redis DataBase Backup),各自拥有独特的优点和局限性。AOF通过实时记录所有写操作指令来提供更高的数据持久性,但这种方式通常需要更多的磁盘空间和更长的数据恢复时间。相对而言,RDB通过在特定时间点创建数据快照来实现数据持久化,这不仅使得数据恢复更快,还通过二进制格式和压缩算法减少了存储需求。不过,RDB方式可能会导致在快照之间进行的数据更改丢失。对于解析和管理这些文件,存在多种工具和配置选项,允许开发者根据具体需求进行灵活的优化。在实际应用场景中,选择哪一种持久化方案(或是两者的结合)应根据数据的大小、访问速度、数据安全性需求以及硬件资源来综合考虑。

参考

  1. Redis 7.0 Multi Part AOF的设计和实现这篇文章由阿里云开发者社区发布,专门探讨了 Redis 7.0 中多部分 AOF 的设计和实现。

  2. Redis Persistence Deep Dive这篇文章深入探讨了 Redis 的持久化机制,对 aof rdb 的特性进行了很多对比,涉及了相关源码的解析。

  3. Xu, Ming, et al. "A Forensic Analysis Method for Redis Database based on RDB and AOF File." J. Comput. 9.11 (2014): 2538-2544.