Redis 有两种持久化数据的机制

  • AOF: 将对数据库所有的「写操作」以追加的方式,写入一个文件当中。待 Redis 重启之后,可以通过这些指令恢复数据
  • RDB: 以生成数据集快照的方式,全量备份数据。生成一个 dump 文件,落盘保存

两种持久化机制可以同时启用,redis-server 默认在启动的时候,会使用它们持久化的数据对自身的数据集进行恢复。但是会优先使用 AOF,因为RDB 在备份的过程中,如果集群出现重启等极端现象,会丢失一部分数据。而 AOF 基本上是间隔一秒执行一次fsync,最大限度的确保不会丢失数据。

RDB 持久化的大致过程

  • redis-server 每隔一段时间就执行一次 BGSAVE 命令
  • redis-server 的主进程会 Fork 一个子进程进行持久化操作
  • 子进程将此时内存中的数据写入进一个临时文件中
  • 写入成功之后,原子的将旧的 rdb 文件替换为新的并删除旧的备份文件

RDB持久化过程享受了 OS 中 COW ( Copy-on-write ) 机制的优势。早期 Linux 内核的 Fork 过程会无脑的直接将父进程的各种资源 Copy 一份给子进程。这种旧的机制在效率上有很大的问题。但是在有了 COW 机制之后,若父进程的内存段中的内容都没有修改,那么就只会分配给子进程很少的一部分资源,如进程描述符等等。实际上此时父子进程是共享内存地址空间的,达到了资源共享的效果。若父子进程共享的内存中的内容有修改,才会真正的去复制一份内存当中的内容给子进程.

所以,你会发现,在你的redis-server 没有接受任何写操作的时候,你执行 bgsave 和 save 是非常快的。但是,若你一遍在疯狂的写入数据,一边在执行bgsave或者 save,他们的执行时间就会随着写入速度的增加而增加,且有可能发生 OOM 的现象。

另外还有一个问题就是:Redis 在执行 bgsave 操作的时候,如果此时还有写操作在执行,那么最新的修改会被同步到新的 RDB 的文件中么?根据 COW 的机制思考一下就可以知道,当发生写入操作之后,父子进程的实际物理内存已经分开了两份,而最终本次 RDB 操作产生的备份文件也是根据子进程内存中的内容来的,所以,可以说 RDB 执行完成后文件中的内容,就是 fork 函数执行那一刻,内存中的内容(此时,父子进程共享内存)

AOF 文件重写大致过程

由于 aof 文件是以追加的形式将写入命令持久化到该文件中,那么这个文件的大小应该一直是一个递增的状态。Redis 为了避免该文件过大,会按照一定的策略执行「AOF 文件重写」的操作,以此保证 aof 文件中是能够恢复数据的最小指令集

  • Redis 执行 fork(),创建一个子进程
  • 子进程复制旧的 aof 文件中的内容,写入至一个临时文件中
  • 在 AOF 持久化期间,当有新的执行命令进来的时候,该命令先被缓存到内存中,然后写入到旧的 aof 文件中
  • 子进程完成对旧 aof 文件的复制,向其父进程付出「完成」信号。父进程将在内存中缓存的新执行的写入命令追加到该临时文件中
  • 原子替换旧的 AOF 文件

需要注意的的是,BGSAVE 和 BGREWRITEAOF(AOF 文件重写)两个命令是不能够同时执行的。原因也很简单,两条命令的执行都会给系统造成比较大的 IO 压力。

RDB 的优点与缺点

优点

  • 对主进程的性能没有影响:使用 RDB 在进行备份的时候通常有两个命令可供选择:SAVE 和 BGSAVE。两个操作主要的区别就在于前者会阻塞主进程,而后者是通过 fork 一个子进程来做的。在使用的过程当中,大家通常会选择 BGSAVE。
  • 很适合做多版本恢复:因为 RDB 是全量备份数据(snapshot),且备份的时间可以自由指定。这就给灾备和多版本备份提供了便利。而 AOF 显然是不具备这一优势的。它总是以当前 aof 文件中的操作去恢复数据,一旦Redis执行过一些清除数据的命令并发生了文件重写之后,如FLUSHALL 等,那么它的记录将不再具有历史追溯性。

    缺点

  • 全量复制:RDB 在持久化的时候,采用的是全量保存数据库的内容。若当前已经有持久化的进程在执行,那么想再次触发持久化操作是会被 ban 的

  • 阶段性复制:是否执行 RDB 持久化是由一些策略决定的,这也就是说,两次 RDB 持久化可能会间隔一段时间。那么在这段时间中,发生一些极端情况,如掉电等等,Redis 数据库可能会丢失一段时间的数据。这对数据完整性要求较高的使用者来说是不能够接受的。

  • 阻塞客户端访问:在需要备份的数据集较大的时候,fork 子进程进行 RDB 持久化处理可能会耗时较长的时间,而在这段时间内,主进程是被block住的,因此,部分客户端的请求可能会被阻塞

  • 无法及时更新:若在执行备份操作期间还有数据写入的话,是不会影响正在执行的 RDB 备份操作的。RDB 备份的数据版本取决于 bgsave 或者 save 执行的那一刻数据库中的「快照」

AOF 的优点与缺点

优点

  • AOF 默认情况下会每秒执行一次fsync,将 redis-server 的写入操作持久化到aof 文件中。最大限度的保证了数据的完整性。即使出现极端情况导致数据丢失,也只会丢失1s的数据。且执行fsync的策略是可以进行设置的,如,每次发生写入操作再进行fsync
  • AOF 以追加的方式进行写入,不会使用seek等移动文件指针的系统调用。即使写入的命令不完整,或者有错误,也有redis-check-aof工具可以进行修复
  • AOF 会在 aof 文件体积过大的时候进行重新,尽量缩减文件当中的命令,保证 aof 文件中的命令是能够恢复 redis-server 数据的最小集。在新的文件重写完成之后,会进行新旧 aof 切换

缺点

  • AOF 文件的体积通常要大于 RDB 文件的体积
  • 由于 AOF 默认每秒都在进行 fsync,那也就是说每秒都会有i/o上面的消耗
  • 在使用 AOF 的过程当中,我觉得是有一个「深坑」的:如果你在误操作执行了 FLUSHALL 命令之后,要么在执行 AOF 重写前到线上机器的 aof 文件中把 FLUSHALL 这行命令的记录给去掉,要么,你就只能接受,aof 文件被重写后只有一条 FLUSHALL 命令的结果了

持久化方式的「理论最佳实践」

这里之所以说是理论最佳实践,是因为用户可能要根据自己的业务需求来选择合适的持久化方式。至于适不适合得经过实践的检验,但目前笔者只能给出理论上的建议。

  • 若用户完全不关心数据的安全性(几乎把 Redis 当做memcache来使用):那么你可以把 AOF 和 RDB 两个持久化机制都关掉。这样在大数据量的情况下,你的 Redis 的性能将会有所提升
  • 若用户可以容忍丢失一部分数据:建议只使用 RDB 的持久化方式。没必要在开启 AOF,这部分性能,资源的损耗完全没有必要
  • 若用户几乎不可以容忍丢失数据:建议 RDB 和 AOF 同时开启。为什么要同时开启而不只开启 AOF 呢?原因有二:首先 AOF 有一个坑,如果只开启 AOF,一旦踩进去,那就不是丢失个几分钟数据的事了。其次,RDB 最有用的一个优点就在于,他可以为灾备和多版本备份提供良好的支持