Redis的数据持久化功能详解(1)—— 两种持久化方式

Redis提供了几种数据持久化选项:

  • 根据指定的时间间隔,RDB(Redis DataBase,Redis数据库)持久化会为你的数据集创建时间点快照。

  • AOF(Append-Only File,只增文件)持久化会记录Redis服务器收到的每个写入操作,当Redis服务器重新启动时,这些写入操作就会被重新执行,这样便能重新建立原始数据集。Redis会以一种只增的方式,将收到的写入命令记录在AOF文件之中,存储的数据格式和Redis协议自身的格式相同。当AOF文件变得太大时,Redis会在后台重写日志文件。

  • 如果你愿意的话,你可以完全禁用Redis的数据持久化功能。此时,只有当Redis服务器正在运行时,你的数据才不会丢失。

  • 你可以在同一个Redis服务器上,将AOF持久化和RDB持久化结合使用。请注意,在这种情况下,当Redis重新启动时,会使用AOF文件重建原始数据集。因为,这个文件可以最大程度地确保数据完整性。

理解RDB持久化和AOF持久化之间的优势和劣势是非常重要的。接下来,本文会介绍RDB和AOF的优劣之处,并且会详述它们的适用场景。

一、RDB持久化的优劣点

1. RDB的优点

  • RDB是一个非常紧凑的文件,Redis以单文件和时间点的方式表示你的数据集。RDB非常适合于备份数据。例如,你可能想要将最近24小时的数据,每隔1小时归档至你的RDB文件之中,并且还想要将最近30日的数据,每隔1日保存为一个RDB快照文件。当Redis发生灾难时,你就可以轻松地恢复不同版本的数据集。

  • RDB持久化非常适合于灾难恢复,它是一个结构紧凑的文件,既可以传输至较远的数据中心,也可以传输至Amazon S3的云主机(有可能会加密)。

  • RDB持久化会尽量提高Redis的性能,因为Redis的父进程只需要创建一个子进程就可以进行数据持久化了,所有的数据持久化工作都是由上述的子进程实现的。父进程从来不会执行磁盘I/O操作,或者其他类似的操作。

  • 相比起AOF持久化,当你的数据集较大时,如果Redis使用RDB持久化,那么它在重新启动时恢复数据集的速度会更快。

2. RDB的缺点

  • 当Redis意外停止工作时(例如,发生停电之后),如果你需要尽量减小数据丢失的可能性,那么RDB并不是一个很好的选择。你可以在产生RDB快照的地方配置不同的保存点(Save Point)。例如,可以在至少经过5分钟且对数据集有100次写入操作时,设置一个保存点(但是,你也可以设置多个保存点)。然而,你通常会每隔5分钟(或更长时间),就创建一个RDB快照。因此,当Redis由于意外关机而停止工作时,你可能会丢失最近几分钟之内的数据。

  • 如果要使用RDB持久化,那么Redis需要经常运行fork()函数,这样便能通过子进程将数据持久化保存在磁盘上。当数据集较大时,fork()函数可能会耗费较多的时间。如果数据集非常大,并且CPU性能不是很好,那么fork()函数可能会导致Redis在短时间内无法为客户端提供服务,这段时间长达若干毫秒,甚至会长达1秒。AOF也需要调用fork()函数,但是你可以调整Redis重写日志文件的频率,不用对Redis的耐用性作出任何折中。

二、AOF持久化的优劣点

1. AOF的优点

  • 当Redis使用AOF持久化时,它的耐用性会更好。你可以配置不同的fsync()调用策略:从不调用fsync()函数、每秒调用一次fsync()函数、每次查询调用一次fsync()函数。当使用默认的fsync()调用策略时,每秒写入的性能仍然非常不错(Redis会使用一个后台线程来调用fsync()函数,当没有任何fsync()函数正在运行时,主线程便会尝试执行写入操作。),你可能只会丢失1秒钟内写入的数据。

  • AOF日志是一种只增日志文件,所以不会有任何查找操作,即使发生停电事故,Redis也不会产生数据破坏的问题。如果由于某些原因(磁盘已满或其他原因)导致AOF日志文件的末尾有一条尚未完成的写入命令,那么你可以使用redis-check-aof工具轻松地修复这个问题。

  • 当AOF日志文件变得太大时,Redis能够在后台自动地重写这个日志文件。这种重写操作是非常安全的。当Redis持续向老日志文件追加日志时,它会创建一个全新的日志文件,这个新日志文件包含用于创建当前数据集的最小操作集,一旦这个新日志文件被创建完成,Redis便会切换至新日志文件,然后向新日志文件追加日志。

  • AOF日志文件包含所有操作的日志,每个操作会逐条记录在这个日志文件之内,这种格式易于理解和解析。你甚至可以轻松地导出一个AOF日志文件。例如,即使你使用FLUSHALL命令误删了所有的数据,如果此时Redis没有进行日志重写操作,那么你还可以挽救误删的数据,只需要停止Redis服务器,删除日志中的最后一条命令,然后再次重新启动Redis服务器即可。

2. AOF的缺点

  • 对于相同的数据集来说,AOF文件的尺寸通常要比同等的RDB文件更大。

  • AOF持久化的速度可能会比RDB持久化更慢,这取决于确切的fsync()函数调用策略。通常,当fsync()调用策略被设置为every second时,AOF持久化的性能仍然非常好;当禁用fsync()调用时,即使在高负载的情况下,AOF持久化的速度应当几乎和RDB持久化一样快。即使在海量写入负载的情况下,RDB仍然能够为最大延迟时间提供更多的保障。

  • 在过去,当我们使用特定的命令(例如,使用一些会造成阻塞的命令,诸如BRPOPLPUSH命令)时,可能会碰到一些罕见的bug,这些bug会导致Redis在重新载入AOF文件时,无法正确地将数据集恢复成先前保存的状态。我们已经在测试套件中进行了一些测试:首先,自动创建若干个随机的复杂数据集;然后,重新载入这些数据集;最后,检查数据集是否被正确恢复。虽然这种bug在AOF持久化中并不常见,但是相比之下,这种bug在RDB持久化中几乎不可能发生。下面将这一点解释的更清楚一些:Redis的AOF持久化会增量更新一个已有的日志文件,就像MySQL或MongoDB一样;而RDB持久化会反复地创建完整的数据快照,这样在概念上显得更加健壮。然而 —— (1)应当注意到,Redis每次重写AOF日志文件时,它会根据数据集包含的实际数据,重建AOF日志文件。相比起总是追加日志的AOF文件(或者,重写AOF日志文件时,读取的是老的AOF文件,而不是内存中的数据),上述方式对于这种bug的抵抗力更加强大。(2)目前,暂时还没有用户在实际应用中碰到AOF文件损坏的情况。

三、两种持久化模型

如果你想要将Redis的数据安全性提升至PostgreSQL能够提供的水平,那么你应当同时使用RDB持久化和AOF持久化。

如果你非常重视你的数据,但是当发生灾害时,仍然可以容忍几分钟内的数据丢失,那么你可以只单独使用RDB持久化。

有很多用户会单独使用AOF持久化,但是我们并不推荐这么做。如果你想要备份数据库,那么经常创建RDB快照是一种很不错的方法,这样能使得Redis服务器的重启速度更快,并且能避免AOF引擎的潜在bug。

注意:由于前文所述的各种原因,Redis未来可能会将AOF持久化和RDB持久化合并为一个持久化模型(这是个长期计划)。

下文将会更加详细的描述这两种持久化模型的使用方法和工作原理。

1. RDB快照

在默认情况下,Redis会将数据集的快照保存在磁盘上,保存为一个名为dump.rdb的二进制文件。你可以将Redis配置为每隔N秒,并且对数据集至少有M次修改时,才创建快照。你也可以手动调用SAVE命令或BGSAVE命令。

举个例子,如果你想要让Redis每隔60秒,并且对数据集至少有1000次修改时,自动在磁盘上创建数据集快照,那么你可以在redis.conf配置文件中进行如下配置:

  1. save 60 1000

这种持久化策略被称为快照。RDB快照是如何工作的呢?无论Redis何时需要在磁盘上创建数据集的快照,都会进行如下操作:

  • Redis会调用fork()函数。此时,Redis会有一个子进程和一个父进程。
  • 子进程开始将数据集写入一个临时的RDB文件。
  • 当子进程写入新的RDB文件结束时,它会用这个新的RDB文件替换老文件。

这种方法使得Redis能够从写时拷贝(copy-on-write)的语义中受益。

2. AOF文件

RDB快照的耐用性不是非常好。如果你的Redis服务器突然停机,或者你的电源线发生故障,或者你误执行kill -9命令,那么最近写入Redis的数据将会丢失。虽然这对于某些应用程序来说可能不是一个大问题,但是要求耐用性完整的用例也是存在的,Redis对于这些用例并不是一个可行的选择。

只增文件是一种可选的解决方案,它为Redis提供了完整的耐用性。Redis从1.1版本开始提供这项功能。

你可以在redis.conf文件中启用AOF持久化,如下所示:

  1. appendonly yes

从现在开始,Redis每次接收到一个修改数据集的命令(例如,SET命令)时,它都会将这个命令追加在AOF日志末尾。当你重新启动Redis服务器时,Redis会重新执行AOF日志中的命令,这样便能重建缓存状态。

2.1 日志重写

正如你可以猜到的,随着写入操作的不断执行,AOF文件会变得越来越大。例如,如果你将一个计数器递增100次,那么执行结束之后,你的数据集中最终只有一个键,通过这个键能够获取上述计数器的值。但是,你的AOF文件中将会有100个命令条目。其中,有99个命令条目对于重建当前的缓存状态来说,并没有什么用处。

因此,Redis支持一个有趣的特性:Redis能够在后台重建AOF文件,而不会中断为客户端提供的服务。你可以随时执行BGREWRITEAOF命令,Redis会将用于重建当前数据集的内存状态的最短命令序列重新写入AOF文件。如果你使用Redis 2.2版本,那么当使用AOF持久化时,你需要时不时地执行BGREWRITEAOF命令。从2.4版本开始,Redis已经能够自动触发重写日志的操作了。若要了解更详细的信息,你可以参考2.4版本的示例配置文件。

2.2 AOF的耐用性

你可以配置Redis调用fsync()函数的频率,每次调用都会将数据同步至磁盘:Redis支持三个选项:

  • 每当将一个新的命令追加至AOF文件时,就调用一次fsync()函数。非常非常慢,但非常安全。

  • 每秒钟调用一次fsync()函数。速度足够快(2.4版本几乎和RDB快照一样快),如果发生灾难,你只会丢失1秒钟内的数据。

  • 从不调用fsync()函数,由操作系统负责内存和磁盘的数据同步。这个选项的运行速度更快,但是安全性较差。

建议使用每秒调用一次fsync()函数的策略(上文的第二个选项),这也是Redis的默认配置。这种策略能够兼顾速度和安全性。实际上,总是调用fsync()函数的策略(上文的第一个选项)是非常慢的(虽然在Redis 2.0版本中有所改善)—— 因为没有任何办法可以提升fsync()函数自身的运行速度。

2.3 如何修复损坏的AOF文件

当写入AOF文件时,Redis服务器有可能会崩溃(这种情况应当从来不会导致数据不一致),此时便有可能损坏AOF文件,Redis也就不再能加载这个AOF文件了。当发生这种事故时,你可以通过以下过程修复这个问题:

  • 备份拷贝你的AOF文件。
  • 使用Redis自带的redis-check-aof工具,修复原来的AOF文件。
  • 使用diff -u命令,检查修复前后的两个文件的差别。这一步是可选的。
  • 重新启动Redis服务器,此时Redis便会使用已修复的AOF文件来重建缓存。
2.4 工作原理

日志重写会使用写时拷贝策略,这一点和创建快照相同。AOF日志重写的工作流程如下所示:

  • Redis调用fork()函数。此时,Redis会有一个子进程和一个父进程。
  • 子进程开始在一个临时文件中写入新的AOF日志。
  • 父进程会在一个内存缓冲区中积累数据集所有的新变化(同时也会将这些新变化写入老的AOF文件之中,如果日志重写失败,仍然能够确保AOF持久化是安全的)。
  • 当子进程完成重写文件时,父进程会收到一个信号,然后便会将内存缓冲区中的日志追加在子进程生成的临时文件的末尾。
  • 最后,Redis会使用老文件的文件名重新命名新的AOF文件,然后开始将新的数据追加在这个新文件的末尾。
2.5 如何从dump.rdb快照切换至AOF文件

想要从已有的dump.rdb快照生成AOF文件,在Redis 2.0和Redis 2.2中的做法是不同的。显而易见的是,Redis 2.2的实现流程更加简单,并且完全不需要重新启动Redis服务器。

Redis >= 2.2的实现流程:

  • 为最新的dump.rdb文件创建一个备份。
  • 将这个备份文件传输至某个安全的地方。
  • 在操作系统的Shell中运行以下两个命令:
  1. ## 启用AOF持久化功能
  2. redis-cli config set appendonly yes
  3. ## 清除以前设置的保存点
  4. redis-cli config set save ""
  • 确保Redis数据库中包含的键的数量和dump.rdb中包含的键的数量相同。
  • 确保写入的日志被正确地追加在AOF文件的末尾。

第一个CONFIG命令会启用AOF持久化功能。为此,Redis将会阻塞,以便于生成初始的转储文件,然后将会打开这个文件等待写入,并且将会追加所有接下来的写入查询。

第二个CONFIG命令会关闭RDB持久化功能。如果你想要同时启用RDB持久化和AOF持久化,那么也可以不执行这个步骤。

重要提示:不要忘记编辑redis.conf配置文件,在这个文件中启用AOF持久化功能。否则,当你重新启动服务器时,Redis将会丢失先前临时修改的配置,服务器重启时还是会使用老的配置信息。

Redis 2.0的实现流程:

  • 为最新的dump.rdb文件创建一个备份。
  • 将这个备份文件传输至某个安全的地方。
  • 停止Redis数据库的所有的写入操作。
  • 在操作系统的Shell中运行以下命令,这样将会创建AOF文件:
  1. redis-cli bgrewriteaof
  • 当Redis生成AOF文件之后,停止服务器的运行。
  • 编辑redis.conf文件,启用AOF持久化功能。
  • 重新启动Redis服务器。
  • 确保Redis数据库中包含的键的数量和dump.rdb中包含的键的数量相同。
  • 确保写入的日志被正确地追加在AOF文件的末尾。

四、AOF和RDB之间的相互影响

在Redis 2.4及其以上的版本中:当Redis正在执行RDB快照操作时,它会避免触发AOF重写操作;当Redis正在执行AOF重写操作时,它会不允许执行BGSAVE命令。这种行为可以防止这两个Redis后台进程同时执行繁重的磁盘I/O操作。

当Redis正在创建快照时,如果用户使用BGREWRITEAOF命令,显式地请求Redis执行日志重写操作,那么Redis服务器将会返回一个OK状态码,告诉用户日志重写操作已经被调度了,一旦快照创建完成,就会立即执行日志重写操作。

如果同时启用AOF持久化和RDB持久化,那么当Redis服务器重新启动时,它会使用AOF文件重建原始的数据集。因为,AOF文件能够保证恢复最完整的数据集。

五、备份Redis的数据

在开始阅读本节之前,请确保读过下面这句话:一定要备份你的数据库。磁盘损坏、在云端运行的实例丢失,以及等等:没有备份意味着需要承担数据丢失的巨大风险。

Redis非常易于进行数据备份,因为你可以在数据库运行时拷贝RDB文件。RDB文件一旦被创建就不会再被修改了,当Redis正在创建快照时,它会使用一个临时的文件名,只有当新的快照创建完成之后,Redis才会使用rename(2)函数将新的快照文件重新命名为最终的目标文件名。

这就意味着,在Redis服务器运行时拷贝RDB文件是完全安全的。以下是我们建议使用的数据备份方法:

  • 在你的服务器上创建一个定时任务(Cron Job),每小时在一个目录中创建一个RDB快照文件,并且每天在另一个目录中创建一个RDB快照文件。
  • 定时任务的脚本每次运行时,一定要调用find命令,确保删除太老的快照文件:例如,你可以保留最近48小时之内的每小时快照和最近1个月或两个月之内的每日快照。快照的文件名最好包含数据和时间相关的信息。
  • 要将一个RDB快照文件传输至你的Redis实例所属的数据中心或物理机之外的另一台服务器上,这项操作每天至少执行一次。

六、灾难恢复

在使用Redis时,灾难恢复和数据备份基本上是同一件事情,加上在很多不同的外部数据中心之内传输这些备份文件的能力。在这种情况下,如果主数据中心有Redis服务器正在运行和创建快照,那么即使主数据中心遭遇毁灭性的灾害,Redis的数据也是非常安全的。

由于有很多Redis用户正处于创业起步阶段,因此并没有足够的财力承担上文描述的数据备份和灾难恢复的方法。我们会在下文讨论一些花费较少的灾难恢复技术:

  • 如果你想要在云端挂载你的灾难恢复系统,那么Amazon S3或其他类似的服务都是不错的选择。你可以将你的每日或每小时的RDB快照文件进行加密,然后传输至云端。你可以使用gpg -c命令对你的数据进行加密(使用对称加密模式)。确保你的密码存储在很多不同的安全的地方(例如,给你的公司或单位中最重要的人一份密码的拷贝)。为了提高数据的安全性,建议使用多种存储服务。

  • 将你的快照文件通过SCP(SSH的一部分)传输至远端服务器。这是一种简单和安全的方法:在远端的某处获取一台VPS服务器,然后安装SSH,并且生成一个不带密码的SSH客户端密钥,然后将这个密钥添加至VPS服务器的authorized_keys之中。现在,你可以通过一种自动化的方式,将备份文件传输至VPS服务器了。为了达到最佳的效果,你需要从不同的供应商获取至少两台VPS服务器。

如果没有以正确的方式进行编码,上述的备份/恢复系统就很有可能出现故障,这一点非常重要。至少要确保在备份文件传输完成之后,你能够验证文件的大小(应当和你拷贝的备份文件相同),如果你正在使用VPS服务器,那么还要验证文件的SHA1摘要值。

如果由于某些原因导致备份文件传输失败,那么你还需要某种类型的独立的报警系统。