Redis RDB 持久化详解

兴发娱乐官网

作者:李小兵

程序员日历小兵

Redis是一个内存数据库,可以将数据存储在内存中,并且比在磁盘上存储数据的传统数据库快得多。但是一旦进程退出,Redis的数据就会丢失。

为了解决这个问题,Redis提供了两种RDB和AOF持久性方案,用于将内存数据保存到磁盘以避免数据丢失。

Antirez在《Redis 持久化解密》中表示,通常有三种持久性策略可以防止数据损坏:

方法1是数据库不关心故障,数据文件损坏后数据备份或快照恢复数据。 Redis的RDB持久性就是这样。方法二是使用数据库的操作日志,并记录每次操作行为,以便在失败后通过日志恢复到一致状态。因为操作日志是按顺序添加的顺序写入的,所以不存在无法恢复操作日志的情况。第三种方法是数据库不修改旧数据,而只是以附加方式执行写操作,这样数据本身就是一个日志,因此数据无法恢复。 CouchDB是这种方法的一个很好的例子。

RDB是第一种方法,它是将Redis进程的当前时间点快照保存到存储设备的过程。

使用RDB

RDB触发机制分为手动触发和指令以及使用redis.conf配置自动触发。

手动触发Redis for RDB持久性的命令是:

保存,该指令阻止当前的Redis服务器。在保存指令期间,Redis无法处理其他命令,直到RDB过程完成。 Bgsave,执行命令时,Redis在后台异步执行快照操作。此时,Redis仍然可以请求相应的客户端。具体操作是Redis进程执行fork操作以创建子进程。 RDB持久性进程负责子进程,并在完成后自动结束。 Redis仅在叉子期间阻塞,但通常很短。但是,如果Redis有大量数据,则fork时间会更长,内存将会翻倍。这需要特别注意。

自动触发RDB的默认配置如下:

4aacb2c39c604d75a53653574dbb7e5a

如果您不需要Redis进行持久化,则可以取消注释所有保存行以禁用保存功能,也可以通过空字符串禁用持久性:save''。

Redis服务器周期操作功能serverCron每100毫秒执行一次。此功能用于正在运行的服务器上的维护。?淙挝裰皇羌觳槭欠衤闫渲幸桓霰4娌考H绻悖蛑葱衎gsave指令。

RDB整体流程

在了解了RDB的基本用法之后,我们将继续了解有关RDB持久性的更多信息。在此之前,我们可以考虑如何实现持久性机制。毕竟,这是许多中间件需要的模块。

首先,保存的文件内容结构的持久性必须紧凑,特别是对于数据库,需要持久化的数据量非常大,并且必须确保持久文件不会占用太多存储。其次,当持久化时,中间件也应该快速响应用户请求,持久化操作应该最小化中间件的其他功能。最后,持久性毕竟会消耗性能,如何平衡性能和数据安全性,以及如何灵活配置触发器持久性操作。

接下来,我们将采用这些问题并在源代码中寻求答案。

本文中的源代码来自Redis 4.0,RDB持久性进程的相关源代码位于rdb.c文件中。大致过程如下图所示。

5b028cec1c1e4704a7f22b23d7f904b9

上图显示了触发RDB持久性的三种方法之间的整体关系。 serverCron自动触发的RDB等同于直接调用bgsave指令的进程。在bgsave处理过程启动子进程之后,调用save指令的处理流程。

让我们从serverCron自动触发逻辑开始。

自动触发RDB持久性

77ecc55c2a7245129d32e13070c8e84d

如上图所示,redisServer结构的save_params指向具有三个值的数组,其值对应于redis.conf文件中的保存配置项。它们是save9001,save30010和save6010000。脏记录已更改的键值,并且lastsave记录最后的RDB持续时间。

serverCron函数遍历数组的值并检查当前Redis状态是否与触发RDB匹配。例如,自上次RDB持久性以来,900数据已发生变化。

e8aa350d1b02448cadafa6a40d256e6a

如果满足触发器RDB,serverCron将调用rdbSaveBackground函数,该函数是bgsave命令将触发的函数。

子进程背景RDB持久性

执行bgsave指令时,Redis将首先触发bgsaveCommand以在调用rdbSaveBackground之前执行当前状态检查,其逻辑如下所示。

a745892c1b7f46c3bc54bfe6625b7014

rdbSaveBackground函数的主要工作是调用fork命令生成子进程,然后在子进程中执行rdbSave函数,该函数是save指令最终触发的函数。

70018db0fb924996b8eb88c8287a7376

为什么Redis使用子进程而不是线程作为后台RDB的东西。因此,为了避免使用锁来降低性能,Redis选择启动一个新的子进程,根据执行的RDB持久性独立拥有父进程的内存副本。

但是,应该注意fork将占用一定的时间,并且父进程和子进程占用的内存是相同的。当Redis键值很大时,fork需要很长时间。在此期间,Redis无法响应其他命令。此外,Redis占用了两倍的内存空间。

生成RDB文件并将其保留到硬盘驱动器

Redis的rdbSave函数是一个真正的RDB持久化函数,其一般流程如下:

首先打开一个临时文件,调用rdbSaveRio函数,将当前Redis内存信息写入临时文件,然后调用fflush,fsync和fclose接口将文件写入磁盘,并将临时文件重命名为官方RDB文件。最后,记录诸如脏和lastsave的状态信息。 serverCron使用此状态信息。1251fc24ddeb4aae8bbc063fbcae9cfa

以下是fflush和fsync之间差异的简要说明。它们都用于刷缓存,但层次结构不同。 fflush函数用于FILE *指针,用于将缓存的数据从应用程序层缓存刷新到内核,而fsync函数更低级,作用于文件描述符以将内核缓存刷新到物理设备。

内存数据到RDB文件

rdbSaveRio以相对紧凑的格式将Redis内存中的数据写入文件,如下图所示。

e03ae699ba2e406da5346042bb995cf2

编写rdbSaveRio函数的一般流程如下:

首先写入REDIS法术力,然后写入RDB文件的版本(rdb_version)和额外的辅助信息(aux)。辅助信息包括Redis版本,内存使用和复制库(repl-id)和偏移量(repl-offset)。然后rdbSaveRio遍历所有当前的Redis数据库并依次写入数据库信息。首先编写RDB_OPCODE_SELECTDB ID和数据库编号,然后写入RDB_OPCODE_RESIZEDB ID以及数据库密钥的数量和要使其无效的密钥数。最后,它将遍历所有键并依次写入它们。当写入键值时,当键值具有死区时间时,首先写入RDB_OPCODE_EXPIRETIME_MS识别码和到期时间,然后写入键值类型的识别码,最后写入键和值。在写入数据库信息之后,还写入Lua相关信息,最后写入RDB_OPCODE_EOF终结符标识符和检查值。0c64d742813b4995b4aa65de16222366

1d78eaebd788448ba9376f73ea3cf50f

当rdbSaveRio写入键值时,它会调用rdbSaveKeyValuePair函数。此函数将依次写入键值的到期时间,键的类型,键和值。

931c42fa27134863a7f0530f5aa7b6c8

根据不同类型的键写入不同的格式,各种键值的类型和格式如下。

30f4b2d0110644c09a1cfcaa4ed99a06

Redis有一个庞大的对象和数据结构系统,它使用六个底层数据结构来构建包含字符串对象,列表对象,哈希对象,集合对象和有序集合对象的对象系统。

不同的数据结构具有不同的RDB持久性格式。今天我们只看看如何持久化集合对象。

99c5bbd3bf7d48c0a0eebaecada96522