现在,我们已经知道 Redis 持久化的两种方式(RDB 与 AOF)的优缺点了。那么,我们该选用哪一种呢?

前言

一般来说,如果想拥有能与 PostgreSQL 相提并论的数据安全性的话,你应该同时使用这两种持久化方式。

另一种常见的情况是,你很关心自己数据,但在故障时也可以接受几分钟的数据丢失,那么你只需要使用 RDB 就够了。

不过,也有很多用户只用 AOF,我们并不建议这样做,因为时不时地生成一个 RDB 快照是个好主意。无论是考虑到数据备份,还是为了重启时更快的数据恢复速度,抑或是考虑到 AOF 引擎可能会出现错误。

下面的部分将介绍更多关于这两种持久化方法的使用细节。

快照(Snapshots / RDB)

Redis 默认在磁盘中保存数据集的快照,它们会被保存在一个名为 dump.rdb 的二进制文件中。

你可以设置 Redis,让它每隔 N 秒钟保存一次数据集,如果这段时间内至少有 M 次数据集修改的话。

或者你也可以手动调用 SAVEBGSAVE 命令。

举例来说,下面的配置将会让 Redis 每隔 60 秒自动保存一次数据集,如果这段时间内至少有 1000 个 key 发生改变的话。

save 60 1000

这个策略也被称为 快照

工作原理

每当 Redis 需要保存数据集到磁盘中时,它会执行下面一系列操作:

  • Redis 会 fork 一个子进程。
  • 子进程开始把数据集写入到一个临时的 RDB 文件中。
  • 当子进程写完了新的 RDB 文件,它会替换旧的 RDB 文件。

如此一来,Redis 可以从 copy-on-write 机制中受益了。

仅追加文件(AOF)

快照的持久化程度还不够高。如果你的电脑运行的 Redis 停止,或者你的电源线坏了,抑或是你不小心执行 kill -9 杀死了你的 server 实例,那么最近写入 Redis 的数据将会丢失。

虽然对于某些应用来说,这也不是什么大事;但对于另一些应用来说,它们可能非常依赖于持久化。

在这种情况下,仅仅依靠快照来保证数据不丢失是不可行的。

仅追加文件(AOF)是 Redis 的另一种持久化策略,它能够实现完全的持久化。Redis 自从 1.1 版本开始就支持 AOF 了。

你可以在配置文件中开启 AOF,就像下面这样:

appendonly yes

从现在开始,每当 Redis 接收到一个改变数据集的命令(比如 SET 命令),它都会被追加到 AOF 文件中。

当 Redis 重启时,它将会重放 AOF 文件来重新构建数据集。

从 7.0.0 版本开始,Redis 引入了分段 AOF 机制。

分段 AOF 机制就是把单个 AOF 文件分割成了基准文件(最多一个)和增量文件(可能不止一个)。

当 AOF 文件被重写时,基准文件代表了一个初始的(RDB 或 AOF 格式的)数据快照,而增量文件里则包含了上一个基准文件创建后的所有增量变更。

所有的这些文件都被放置在一个独立的目录中,并被一个清单文件追踪。

日志重写

随着写操作的执行,AOF 文件会越来越大。

举个例子,如果你对计数器增加 100 次,你只会有一个包含最终结果的 key,但是你的 AOF 文件中却会有 100 条记录。

当重建数据集的时候,这 100 条中的 99 条其实都是不必要的

日志重写是完全安全的。Redis 会持续地往旧的文件中追加操作日志,与此同时,一个新的文件将被创建。这个新的文件里包含了构建当前数据集所需的最小集合。

当这个新文件准备就绪时,Redis 会切换这两个文件,然后开始附加命令到新文件中。

这样一来,Redis 支持了一个有趣的特性:它可以在后台重建 AOF 文件,而不会中断对客户端的服务。

不管何时,只要你发送了 BGREWRITEAOF 命令,Redis 就会把重建当前数据集所需的最短命令序列写入到内存中。

如果你正在使用 Redis 2.2 版本,你需要时不时地运行 BGREWRITEAOF 命令。因为 Redis 从 2.4 版本开始才支持自动触发日志重写(你可以查看示例配置文件了解更多信息)。

从 7.0.0 版本开始,每当要 AOF 重写,Redis 父进程都会开启一个新的增量 AOF 文件来继续写入。子进程执行重写逻辑,并生成一个新的基准 AOF 文件。

Redis 将会使用一个临时的清单来追踪新生成的基准文件和增量文件。当它们准备就绪,Redis 会执行一个原子替换操作,让这个临时文件开始生效。

为了避免在重复失败、AOF 重写重试等情况下,创建太多增量文件的问题,Redis 引入了一个 AOF 重写限制机制,以此来保证失败的 AOF 重写的重试频率会越来越低。

AOF 的 fsync

你可以设置 Redis 使用 fsync 来写数据到磁盘上的次数。下面是它的三个选项:

  • appendfsync always:每当新命令追加到 AOF 文件中时,都会调用 fsync。这会非常非常慢,但是也非常安全。注意,每当执行完来自多个客户端的一批命令,或是执行了管道后,命令才会追加到 AOF 文件中。所以,这意味着每次写入命令都对应着一次 fsync 的调用(在服务端发送响应给客户端之前)。
  • appendfsync everysec:每秒钟调用一次 fsync。这足够快了(2.4 版本之后可能与快照一样快)。并且,假如发生故障,你最多也只会丢失 1 秒钟的数据而已。
  • appendfsync no:禁用 fsync。把数据交给操作系统管理。这是最快的,也是最不安全的做法。通常,Linux 系统会每隔 30 秒把数据刷新到磁盘中,不过这取决于内核具体是怎么设置的。

建议的(也是默认的)策略是每秒钟调用一次 fsync。这是一个既快又安全的选项。

如果选用 always 的话,实践中表明它太慢了,但是它支持组提交group commit。因此如果有多个并行的写操作,Redis 会尝试只调用一次 fsync

AOF 截断

存在一种可能的情况,AOF 正在写入时 server 突然崩溃了,或者储存 AOF 的数据盘写满了。这时候 AOF 仍然包含了某个时间点的连续数据(默认的 AOF fsync 下可能已经是一秒钟以前的旧数据了),但是 AOF 的最后一条命令却被截断了。

最新的 Redis 主要版本仍能加载 AOF 文件,只不过是忽略了最后一条格式错误的命令而已。

在这种情况下,server 会打印出一条日志,类似下面这样:

* Reading RDB preamble from AOF file...
* Reading the remaining AOF tail...
# !!! Warning: short read while loading the AOF file !!!
# !!! Truncating the AOF at offset 439 !!!
# AOF loaded anyway because aof-load-truncated is enabled

你可以改变这个默认配置,让 Redis 停止加载不完整的 AOF 文件。不过默认配置下,Redis 会忽略最后一条命令是否符合格式,仍然继续加载它,以此来保证重启后的可用性。

在这种情况下,老版本的 Redis 可能不会自动恢复数据,你可能需要执行下面的步骤:

  • 备份 AOF 文件。
  • 使用 Redis 附带的 redis-check-aof 工具来修复原始的 AOF 文件:
$ redis-check-aof --fix <filename>
  • (可选)使用 diff -u 命令来比较这两个文件的差异。
  • 有了修复后的文件,重启 Redis server。

AOF 混乱

如果 AOF 文件不仅是被截断了,而是被插入的无效的字节序列搞混乱了,那么情况就要复杂得多。

Redis 会在启动时报错并终止,就像下面这样:

* Reading the remaining AOF tail...
# Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>

这时候,你唯一能做的事就是运行 redis-check-aof 工具,并不用 --fix 选项,然后试图理解问题出在哪里,跳转到它提示的位置,看看是否可能手动修复文件。

AOF 使用和 Redis 协议相同的格式,因此它很容易手动修复。

如果不行的话,你可以尝试让这个工具为我们修复(加上 --fix 选项),但是这样一来,AOF 文件中的出问题的位置之后的部门都会被丢弃,这也就意味着,如果错误发生在文件的开头部分,你将丢失大量的数据。

工作原理

日志重写使用了 copy-on-write 机制,这个机制在快照中已经被使用。以下是它的工作方式:

Redis 版本 >= 7.0.0

  • Redis fork 一个子进程。
  • 子进程开始在一个临时文件中写新的基准 AOF。
  • 父进程打开一个新的增量 AOF 来继续写更新。如果重写失败,旧的基准和增量文件(如果存在的话)加上这个新打开的增量的 AOF 共同代表了完整的数据集,因此数据是安全的。
  • 当子进程写完了基准文件,父进程会收到一个信号,然后使用新打开的增量文件和子进程生成的基准文件来构建一个临时清单,并将它保存。
  • 准备就绪!现在 Redis 可以执行一个清单文件的原子交换,这样 AOF 重写的结果就会生效了。Redis 同时会清理旧的基准文件,以及所有不再使用的增量文件。

Redis 版本 < 7.0.0

  • Redis fork 一个子进程。
  • 子进程开始在一个临时文件中写新的 AOF。
  • 父进程将所有增量的新变更都暂存到一块内存缓冲里(同时它也在旧的 AOF 文件中写入,如此一来,当重写失败时,我们也是安全的)。
  • 当子进程写完了新的 AOF,父进程会收到一个信号,然后将内存中的增量缓冲追加到新的 AOF 文件的末尾。
  • Redis 自动把临时文件(新文件)重命名为“旧文件名”(详见 GitHub Issue #1890 ),然后开始往新文件中写入数据。

从 RDB 切换到 AOF

Redis 2.0 之前和之后版本的做法相差很大。你可以猜想在 Redis 2.2 版本之后切换变简单了,因为它不再需要重启了。

Redis 版本 >= 2.2

  • 备份你最新的 dump.rdb 文件。
  • 保存备份到一个安全的地方。
  • 执行下面这两个命令:
redis-cli config set appendonly yes
redis-cli config set save ""
  • 确保你的数据库中包含了相同数量的 key。
  • 确保所有写入都正确地追加到了 AOF 末尾。

第一个 CONFIG 命令启用了 AOF 持久化。

第二个 CONFIG 命令用于关闭镜像(RDB)持久化。这是可选的,如果你愿意的话,你也可以保持开启这两个持久化方式。

注意:请不要忘记在你的 redis.conf 文件中打开 AOF。不然当你重启 server 时,配置修改会丢失,server 会再次使用旧的配置。

Redis 版本 == 2.0.0

(省略)

AOF 与 RDB 的交互

Redis 2.4 及以上版本确保了当 RDB 快照操作进行中时,不会触发 AOF 重写。同样,在 AOF 重写进行中,不允许 BGSAVE 命令执行。

这么做避免了两个 Redis 后台进程同时进行大量的磁盘 I/O 操作。

当快照操作进行中,且用户使用 BGREWRITEAOF 命令显式地请求日志重写操作时,server 会返回 OK 状态码,告诉用户操作已经接收,然后重写将在快照操作结束后马上开始。

当 AOF 和 RDB 持久化方式都开启时,Redis 会使用 AOF 文件来在重启时构建原始数据集,因为 AOF 能够确保数据是最完整的。

后语

本文介绍了 RDB 与 AOF 各自的特点、使用场景和工作原理,还着重介绍了 AOF 使用过程中可能遇到的问题及其解决方法,以及 RDB 与 AOF 之间的交互。

相信你在阅读完本文之后,可以根据自己的需要,选择合适的 Redis 持久化方式了吧!

下一篇文章,我将介绍 Redis 的数据备份灾难恢复。保持关注喔!

参考


本文使用 CC BY-SA 4.0 国际协议 进行许可,欢迎 遵照协议规定 转载。
作者:六开箱
链接:https://lkxed.github.io/posts/redis-persistence-usage/