Redis持久化总结整理

我们先来分析一下数据库写操作的过程。

  1. 客户端向服务端发送写操作(数据在客户端的内存中)
  2. 数据库服务端接收到写请求的数据(数据在服务端的内存中)
  3. 服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)
  4. 操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)
  5. 磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)

下面我们结合上面的5个流程看一下各种级别的故障。

  1. 当数据库系统故障时,这时候系统内核还是正常运行的,此时只要执行完了第iii步,数据就是安全的,操作系统会完成后面几步,保证数据最终会落到磁盘上。
  2.  当系统断电,这时候上面5项中提到的所有缓存都会失效,并且数据库和操作系统都会停止工作,数据都会丢失,只有当数据在完成第v步后,机器断电才能保证数据不丢失。

redis是一个支持持久化的内存数据库,需要经常将内存中的数据同步到磁盘来保证持久化。redis支持两种持久化方式

  • Snapshotting(快照)也是默认方式– RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照。
  • Append-only file(aof)的方式– AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。Redis 还可以在后台对 AOF 文件进行重写,使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。还可以同时使用 AOF 持久化和 RDB 持久化, 当 Redis 重启时, redis会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。

下面分别介绍:

RDB

默认redis是会以快照的形式将数据持久化到磁盘的,在配置文件中设置持久化规则的格式是:

工作流程:

  1. redis调用fork,现在有了子进程和父进程。
  2. 父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照。
  3. 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。

client 也可以使用save或bgsave命令通知redis做一次快照持久化。SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求,这种方式会阻塞所有client请求,不推荐使用。和SAVE命令直接阻塞服务器进程的做法不同,BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求,但是在BGSAVE命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令的方式和平时有所不同:

  1. 在BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令和BGSAVE命令同时执行是为了避免父进程(服务器进程)和子进程同时执行两个rdbSave调用,防止产生竞争条件。
  2. 在BGSAVE命令执行期间,客户端发送的BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竞争条件。
  3. BGREWRITEAOF和BGSAVE两个命令不能同时执行:如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据,这样如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。

由于快照方式是在一定间隔时间运行一次,所以如果redis意外down掉的话,会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用aof持久化方式,且规则配置为每次修改写入。服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

 AOF

在redis异常退出时,最近的数据会丢失,业务量很大时,丢失的数据是很多的。Append-only方法可以做到全部数据不丢失,但会影响redis的性能。AOF通过保存Redis服务器所执行的写命令来记录数据状态做到全程持久化,当redis重启时,将会读取AOF文件进行恢复到redis关闭前的最后时刻。aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是 appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于os会在内核中缓存 write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要 通过fsync函数强制os写入到磁盘的时机。有三种方式如下(默认是:每秒fsync一次)

对于pipeline的操作(客户端一次性发送N个命令,然后等待这N个命令的返回结果被一起返回),意味着放弃了对每一个命令的返回值确认。在这种情况下,N个命令是在同一个执行过程中执行的。所以当设置appendfsync为everysec时,可能会有一些偏差,因为这N个命令可能执行时间超过1秒甚至2秒。但是可以保证的是最长时间不会超过这N个命令的执行时间和。

AOF重写

AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF文件中的内容会越来越多,文件体积也会越来越大,使用AOF文件来进行数据还原所需的时间就越多。为了压缩aof的持久化文件,redis提供了bgrewriteaof命令。该命令实现了AOF文件重写功能,通过该功能,Redis服务器redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件(新的AOF文件),最后替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多。具体过程如下:

  1. redis调用fork ,现在有父子两个进程
  2. 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
  3. 父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
  4. 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
  5. 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。

在重写aof文件的操作中,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件。

总结:

RDB 的优点

  • RDB保存了 Redis 在某个时间点上的数据集。可以随时将数据集还原到不同时间的版本。
  • RDB 适用于灾难恢复:只有一个文件,且内容紧凑,可以在加密后将它传送到别的数据中心保存。
  • RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘 I/O 操作。
  • RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。因为RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作

RDB 的缺点

  • 在redis异常退出时,最近的数据会丢失,当业务量很大时,丢失的数据是很多的。
  • 每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。 在数据集比较庞大时, fork()可能会非常耗时,造成服务器在某毫秒内停止处理客户端; 如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会更长。 虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。

AOF 的优点

  • 使用 AOF 持久化会让 Redis 变得非常持久:可以设置不同的 fsync 策略, AOF 的默认策略为每秒钟 fsync 一次,在这种配置下,Redis 仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据。
  • AOF 文件是一个只进行追加操作的日志文件, 对 AOF 文件的写入不需要进行 seek , 即使日志因为某些原因而包含了未写入完整的命令,redis-check-aof 工具可以轻易地修复这种问题。
  • Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。
  • AOF 文件有序地保存了以 Redis 协议的格式保存了对数据库执行的所有写入操作, 不小心执行了FLUSHALL 命令, 只要 AOF 文件未被重写, 停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

AOF 的缺点

  • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
  • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。

 

比较:

快照方式持久化的粒度有时间和改变key的数量两种,如果持久化的粒度较小,对性能会有较大的影响,因为每次都是dump整个db;如果持久化的粒度较大,则在指定时间内指定数目的数据的持久化无法保证。而aof持久化的粒度是每次会修改db数据的命令,因此粒度是最小的了,跟日志方式有点类似,由于仅记录一条命令,性能也最好,appendfsync everysec 是个权衡。但aof文件做数据恢复时,如果数据量过大,恢复时间无法忍受。

数据导入

Redis是一个内存数据库,无论是RDB还是AOF,都只是其保证数据恢复的措施。所以Redis在利用RDB和AOF进行恢复的时候,都会读取RDB或AOF文件,重新加载到内存中。相对于MySQL等数据库的启动时间来说,会长很多,因为MySQL本来是不需要将数据加载到内存中的。但是相对来说,MySQL启动后提供服务时,其被访问的热数据也会慢慢加载到内存中,通常我们称之为预热,而在预热完成前,其性能都不会太高。而Redis的好处是一次性将数据加载到内存中,一次性预热。这样只要Redis启动完成,那么其提供服务的速度都是非常快的。而在利用RDB和利用AOF启动上,其启动时间有一些差别。RDB的启动时间会更短,原因有两个,一是RDB文件中每一条数据只有一条记录,不会像AOF日志那样可能有一条数据的多次操作记录。所以每条数据只需要写一次就行了。另一个原因是RDB文件的存储格式和Redis数据在内存中的编码格式是一致的,不需要再进行数据编码工作。在CPU消耗上要远小于AOF日志的加载。但服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。

如果由于系统原因导致了AOF损坏,redis无法再加载这个AOF,可以修复:首先做一个AOF文件的备份,复制到其他地方;修复原始AOF文件,执行:$ redis-check-aof –fix ;可以通过diff –u命令来查看修复前后文件不一致的地方;重启redis服务。

 

容灾备份 Redis 数据

磁盘故障, 节点失效, 诸如此类的问题都可能让你的数据消失不见, 不进行备份是非常危险的。

Redis 对于数据备份是非常友好的, 因为你可以在服务器运行的时候对 RDB 文件进行复制: RDB 文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的 RDB 文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用rename(2) 原子地用临时文件替换原来的 RDB 文件。这也就是说, 无论何时, 复制 RDB 文件都是绝对安全的。我们可以创建一个定期任务, 定时备份RDB文件到安全的地方,确保快照的备份都带有相应的日期和时间信息, 至少每天一次, 将 RDB 备份到数据中心之外, 或者至少是备份到你运行 Redis 服务器的物理机器之外。

 

参考资料

《redis设计与实现》

http://www.redis.cn/topics/persistence.html

http://www.shaoqun.com/a/90346.aspx

http://www.cnblogs.com/siqi/p/4245821.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">