游戏开发工具

Redis 持久化(一)

Redis 如何将数据写入磁盘(仅附加文件、快照等)

持久性是指将数据写入持久存储,例如固态磁盘 (SSD)。Redis 本身提供了一系列持久化选项:

  • RDB(Redis 数据库):RDB 持久性以指定的时间间隔执行数据集的时间点快照。

  • AOF(Append Only File):AOF 持久化记录服务器接收到的每个写操作,在服务器启动时再次播放,重建原始数据集。命令使用与 Redis 协议本身相同的格式以仅附加方式记录。当日志变得太大时,Redis 能够在后台 重写日志。

  • 无持久性:如果您愿意,您可以完全禁用持久性,如果您希望您的数据只要服务器正在运行就存在。

  • RDB + AOF:可以在同一个实例中结合 AOF 和 RDB。请注意,在这种情况下,当 Redis 重新启动时,AOF 文件将用于重建原始数据集,因为它保证是最完整的。

最重要的是要了解 RDB 和 AOF 持久性之间的不同权衡。

#关系型数据库优势

  • RDB 是 Redis 数据的一个非常紧凑的单文件时间点表示。RDB 文件非常适合备份。例如,您可能希望在最近的 24 小时内每小时归档一次 RDB 文件,并在 30 天内每天保存一个 RDB 快照。这使您可以在发生灾难时轻松恢复不同版本的数据集。

  • RDB 非常适合灾难恢复,它是一个可以传输到远程数据中心或 Amazon S3(可能已加密)的压缩文件。

  • RDB 最大限度地提高了 Redis 的性能,因为 Redis 父进程为了持久化而需要做的唯一工作就是派生一个将完成所有其余工作的子进程。父进程永远不会执行磁盘 I/O 或类似操作。

  • 与 AOF 相比,RDB 允许使用大数据集更快地重启。

  • 在副本上,RDB 支持 重启和故障转移后的部分重新同步

#RDB 的缺点

  • 如果您需要在 Redis 停止工作时(例如断电后)将数据丢失的可能性降到最低,那么 RDB 并不好。您可以配置生成 RDB 的不同保存点(例如,在对数据集至少 5 分钟和 100 次写入之后,您可以有多个保存点)。但是,您通常会每五分钟或更长时间创建一个 RDB 快照,因此,如果 Redis 由于任何原因在没有正确关闭的情况下停止工作,您应该准备好丢失最新分钟的数据。

  • RDB 需要经常 fork() 以便使用子进程在磁盘上持久化。如果数据集很大,fork() 可能会很耗时,并且如果数据集很大并且 CPU 性能不是很好,可能会导致 Redis 停止为客户端服务几毫秒甚至一秒钟。AOF 也需要 fork() 但频率较低,您可以调整要重写日志的频率,而不需要对持久性进行任何权衡。

#AOF 优势

  • 使用 AOF Redis 更加持久:您可以有不同的 fsync 策略:根本不 fsync、每秒 fsync、每次查询时 fsync。使用每秒 fsync 的默认策略,写入性能仍然很棒。fsync 是使用后台线程执行的,当没有 fsync 正在进行时,主线程将努力执行写入,因此您只能丢失一秒钟的写入。

  • AOF 日志是一个仅附加日志,因此不会出现寻道问题,也不会在断电时出现损坏问题。即使由于某种原因(磁盘已满或其他原因)日志以写一半的命令结束,redis-check-aof 工具也能够轻松修复它。

  • 当 AOF 变得太大时,Redis 能够在后台自动重写 AOF。重写是完全安全的,因为当 Redis 继续附加到旧文件时,会使用创建当前数据集所需的最少操作集生成一个全新的文件,一旦第二个文件准备就绪,Redis 就会切换两者并开始附加到新的那一个。

  • AOF 以易于理解和解析的格式依次包含所有操作的日志。您甚至可以轻松导出 AOF 文件。例如,即使您不小心使用该 FLUSHALL 命令刷新了所有内容,只要在此期间没有执行日志重写,您仍然可以通过停止服务器、删除最新命令并重新启动 Redis 来保存您的数据集.

#AOF 缺点

  • AOF 文件通常比相同数据集的等效 RDB 文件大。

  • 根据确切的 fsync 策略,AOF 可能比 RDB 慢。一般来说,将 fsync 设置为每秒的性能仍然非常高,并且在禁用 fsync 的情况下,即使在高负载下它也应该与 RDB 一样快。即使在巨大的写入负载的情况下,RDB 仍然能够提供关于最大延迟的更多保证。

Redis < 7.0

  • 如果在重写期间有对数据库的写入,AOF 可能会使用大量内存(这些被缓冲在内存中并在最后写入新的 AOF)。

  • 重写期间到达的所有写入命令都会写入磁盘两次。

  • Redis 可以在重写结束时冻结写入并将这些写入命令同步到新的 AOF 文件。

#好的,那我应该用什么?

您应该使用这两种持久性方法的一般指示是,如果您想要与 PostgreSQL 可以提供的数据安全程度相当的数据安全性。

如果您非常关心您的数据,但在发生灾难时仍然可以忍受几分钟的数据丢失,您可以简单地单独使用 RDB。

有很多用户单独使用 AOF,但我们不鼓励这样做,因为不时拥有 RDB 快照对于进行数据库备份、更快的重启以及 AOF 引擎中出现错误是一个好主意。

以下部分将说明有关这两种持久性模型的更多细节。

#快照

默认情况下,Redis 将数据集的快照保存在磁盘上的一个名为dump.rdb. 如果数据集中至少有 M 次更改,您可以将 Redis 配置为每 N 秒保存一次数据集,或者您可以手动调用 SAVE or BGSAVE 命令。

例如,如果至少更改了 1000 个键,则此配置将使 Redis 每 60 秒自动将数据集转储到磁盘:

save 60 1000


这种策略称为快照

#这个怎么运作

每当 Redis 需要将数据集转储到磁盘时,都会发生以下情况:

  • Redis 分叉。我们现在有一个子进程和一个父进程。

  • 孩子开始将数据集写入临时 RDB 文件。

  • 当孩子写完新的 RDB 文件后,它会替换旧的。

此方法允许 Redis 从写时复制语义中受益。

#仅附加文件

快照不是很耐用。如果您运行 Redis 的计算机停止,您的电源线出现故障,或者您不小心kill -9您的实例,最新写入 Redis 的数据将会丢失。虽然这对某些应用程序来说可能没什么大不了的,但也有完全持久性的用例,在这些情况下,单独的 Redis 快照并不是一个可行的选择。

仅附加文件是 Redis 的另一种完全持久的策略。它在 1.1 版中可用。

您可以在配置文件中打开 AOF:

appendonly yes

从现在开始,每次 Redis 接收到更改数据集的命令(例如 SET )时,它都会将其附加到 AOF。当您重新启动 Redis 时,它将重新播放 AOF 以重建状态。

从 Redis 7.0.0 开始,Redis 使用了多部分 AOF 机制。也就是将原来的单个AOF文件拆分为基础文件(最多一个)和增量文件(可能不止一个)。 基本文件表示重写 AOF 时存在的数据的初始(RDB 或 AOF 格式)快照。增量文件包含自创建最后一个基本 AOF 文件以来的增量更改。所有这些文件都放在一个单独的目录中,并由清单文件跟踪。

#日志重写

随着写入操作的执行,AOF 变得越来越大。例如,如果您将计数器递增 100 次,您最终会在数据集中得到一个包含最终值的键,但在 AOF 中有 100 个条目。重建当前状态不需要其中的 99 个条目。

重写是完全安全的。在 Redis 继续追加到旧文件的同时,使用创建当前数据集所需的最少操作集生成一个全新的文件,一旦第二个文件准备好,Redis 就会切换这两个文件并开始追加到新文件。

所以 Redis 支持一个有趣的特性:它能够在后台重建 AOF 而不会中断对客户端的服务。每当您发出 a BGREWRITEAOF 时,Redis 都会在内存中写入重建当前数据集所需的最短命令序列。如果您将 AOF 与 Redis 2.2 一起使用,则需要 BGREWRITEAOF 不时运行。由于 Redis 2.4 能够自动触发日志重写(有关更多信息,请参阅示例配置文件)。

从 Redis 7.0.0 开始,在调度 AOF 重写时,Redis 父进程会打开一个新的增量 AOF 文件继续写入。子进程执行重写逻辑并生成新的基础 AOF。Redis 将使用一个临时清单文件来跟踪新生成的基础文件和增量文件。当它们准备好后,Redis 会执行原子替换操作,使这个临时清单文件生效。为了避免在 AOF 重写重复失败和重试的情况下创建大量增量文件的问题,Redis 引入了 AOF 重写限制机制,以确保失败的 AOF 重写以越来越慢的速度重试。

#仅附加文件的耐用性如何?

您可以配置 Redis 将 fsync 数据存储在磁盘上的次数。有三个选项:

  • appendfsync alwaysfsync每次将新命令附加到 AOF 时。非常非常慢,非常安全。请注意,在执行来自多个客户端或管道的一批命令之后,这些命令会附加到 AOF,因此这意味着一次写入和一次 fsync(在发送回复之前)。

  • appendfsync everysec:fsync每一秒。足够快(因为 2.4 版本可能和快照一样快),如果发生灾难,您可能会丢失 1 秒的数据。

  • appendfsync no:从不fsync,只是将您的数据交到操作系统手中。更快,更不安全的方法。通常,Linux 将使用此配置每 30 秒刷新一次数据,但这取决于内核的精确调整。

建议(和默认)策略是fsync每秒。它既快速又相对安全。该always策略在实践中很慢,但它支持组提交,因此如果有多个并行写入,Redis 将尝试执行单个fsync操作。

#如果我的 AOF 被截断,我该怎么办?

可能是服务器在写入 AOF 文件时崩溃,或者存储 AOF 文件的卷在写入时已满。发生这种情况时,AOF 仍然包含表示数据集的给定时间点版本的一致数据(使用默认的 AOF fsync 策略可能会旧到一秒),但 AOF 中的最后一个命令可能会被截断。无论如何,最新的主要版本的 Redis 将能够加载 AOF,只需丢弃文件中最后一个格式不正确的命令。在这种情况下,服务器将发出如下日志:

* 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 在这种情况下停止,但默认配置是继续,无论文件中的最后一条命令格式不正确,以保证重启后的可用性。

旧版本的 Redis 可能无法恢复,可能需要执行以下步骤:

  • 制作 AOF 文件的备份副本。

  • redis-check-aof使用Redis 附带的工具修复原始文件:

$ redis-check-aof --fix <filename>
  • 可选地diff -u用于检查两个文件之间的区别。

  • 使用固定文件重新启动服务器。

#如果我的 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 协议,手动修复非常简单。否则可以让实用程序为我们修复文件,但在这种情况下,从无效部分到文件末尾的所有 AOF 部分都可能被丢弃,如果损坏发生,则会导致大量数据丢失在文件的初始部分。

#这个怎么运作

日志重写使用与快照相同的写时复制技巧。这是它的工作原理:

Redis >= 7.0

  • Redis 分叉,所以现在我们有一个子进程和一个父进程。

  • 孩子开始在一个临时文件中写入新的基础 AOF。

  • 父级打开一个新的增量 AOF 文件以继续写入更新。如果重写失败,旧的基础和增量文件(如果有的话)加上这个新打开的增量文件就代表了完整的更新数据集,所以我们是安全的。

  • 当子进程完成基础文件的重写后,父进程会收到一个信号,并使用新打开的增量文件和子进程生成的基础文件来构建临时清单,并将其持久化。

  • 利润!现在 Redis 对清单文件进行原子交换,以便此 AOF 重写的结果生效。Redis 还会清理旧的基础文件和任何未使用的增量文件。

Redis < 7.0

  • Redis 分叉,所以现在我们有一个子进程和一个父进程。

  • 孩子开始在一个临时文件中写入新的 AOF。

  • 父级在内存缓冲区中累积所有新更改(但同时它将新更改写入旧的仅附加文件中,因此如果重写失败,我们是安全的)。

  • 当子进程完成文件重写时,父进程会收到一个信号,并将内存缓冲区附加到子进程生成的文件的末尾。

  • 现在 Redis 以原子方式将新文件重命名为旧文件,并开始将新数据附加到新文件中。

#