Redis AOF 持久化详解

Redis 是一种内存数据库,将数据保存在内存中,读写效率要比传统的将数据保存在磁盘上的数据库要快不少。可是一旦进程退出,Redis 的数据就会丢失。redis

为了解决这个问题,Redis 提供了 RDB 和 AOF 两种持久化方案,将内存中的数据保存到磁盘中,避免数据丢失。RDB的介绍在这篇文章中《Redis RDB 持久化详解》,今天咱们来看一下 AOF 相关的原理。数据库

AOF( append only file )持久化以独立日志的方式记录每次写命令,并在 Redis 重启时在从新执行 AOF 文件中的命令以达到恢复数据的目的。AOF 的主要做用是解决数据持久化的实时性。缓存

RDB 和 AOF

antirez 在《Redis 持久化解密》一文中讲述了 RDB 和 AOF 各自的优缺点:安全

  • RDB 是一个紧凑压缩的二进制文件,表明 Redis 在某个时间点上的数据备份。很是适合备份,全量复制等场景。好比每6小时执行 bgsave 备份,并把 RDB 文件拷贝到远程机器或者文件系统中,用于灾难恢复。
  • Redis 加载 RDB 恢复数据远远快于 AOF 的方式
  • RDB 方式数据没办法作到实时持久化,而 AOF 方式能够作到。

下面,咱们就来了解一下 AOF 是如何作到实时持久化的。bash

AOF 持久化的实现

示意图

如上图所示,AOF 持久化功能的实现能够分为命令追加( append )、文件写入( write )、文件同步( sync )、文件重写(rewrite)和重启加载(load)。其流程以下:服务器

  • 全部的写命令会追加到 AOF 缓冲中。
  • AOF 缓冲区根据对应的策略向硬盘进行同步操做。
  • 随着 AOF 文件愈来愈大,须要按期对 AOF 文件进行重写,达到压缩的目的。
  • 当 Redis 重启时,能够加载 AOF 文件进行数据恢复。

命令追加

当 AOF 持久化功能处于打开状态时,Redis 在执行完一个写命令以后,会以协议格式(也就是RESP,即 Redis 客户端和服务器交互的通讯协议 )将被执行的写命令追加到 Redis 服务端维护的 AOF 缓冲区末尾。网络

好比说 SET mykey myvalue 这条命令就以以下格式记录到 AOF 缓冲中。app

"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
复制代码

Redis 协议格式本文再也不赘述,AOF之因此直接采用文本协议格式,是由于全部写入命令都要进行追加操做,直接采用协议格式,避免了二次处理开销。函数

文件写入和同步

Redis 每次结束一个事件循环以前,它都会调用 flushAppendOnlyFile 函数,判断是否须要将 AOF 缓存区中的内容写入和同步到 AOF 文件中。性能

flushAppendOnlyFile 函数的行为由 redis.conf 配置中的 appendfsync 选项的值来决定。该选项有三个可选值,分别是 alwayseverysecno

  • always:Redis 在每一个事件循环都要将 AOF 缓冲区中的全部内容写入到 AOF 文件,而且同步 AOF 文件,因此 always 的效率是 appendfsync 选项三个值当中最差的一个,但从安全性来讲,也是最安全的。当发生故障停机时,AOF 持久化也只会丢失一个事件循环中所产生的命令数据。
  • everysec:Redis 在每一个事件循环都要将 AOF 缓冲区中的全部内容写入到 AOF 文件中,而且每隔一秒就要在子线程中对 AOF 文件进行一次同步。从效率上看,该模式足够快。当发生故障停机时,只会丢失一秒钟的命令数据。
  • no:Redis 在每个事件循环都要将 AOF 缓冲区中的全部内容写入到 AOF 文件。而 AOF 文件的同步由操做系统控制。这种模式下速度最快,可是同步的时间间隔较长,出现故障时可能会丢失较多数据。

Linux 系统下 write 操做会触发延迟写( delayed write )机制。Linux 在内核提供页缓存区用来提供硬盘 IO 性能。write 操做在写入系统缓冲区以后直接返回。同步硬盘操做依赖于系统调度机制,例如:缓冲区页空间写满或者达到特定时间周期。同步文件以前,若是此时系统故障宕机,缓冲区内数据将丢失。

fsync 针对单个文件操做,对其进行强制硬盘同步,fsync 将阻塞直到写入磁盘完成后返回,保证了数据持久化。

appendfsync的三个值表明着三种不一样的调用 fsync的策略。调用fsync周期越频繁,读写效率就越差,可是相应的安全性越高,发生宕机时丢失的数据越少。

有关 Linux 的I/O和各个系统调用的做用以下图所示。具体内容能够查看《聊聊 Linux I/O》一文。

示意图

AOF 数据恢复

AOF 文件里边包含了重建 Redis 数据所需的全部写命令,因此 Redis 只要读入并从新执行一遍 AOF 文件里边保存的写命令,就能够还原 Redis 关闭以前的状态。

示意图

Redis 读取 AOF 文件而且还原数据库状态的详细步骤以下:

  • 建立一个不带网络链接的的伪客户端( fake client),由于 Redis 的命令只能在客户端上下文中执行,而载入 AOF 文件时所使用的的命令直接来源于 AOF 文件而不是网络链接,因此服务器使用了一个没有网络链接的伪客户端来执行 AOF 文件保存的写命令,伪客户端执行命令的效果和带网络链接的客户端执行命令的效果彻底同样的。
  • 从 AOF 文件中分析并取出一条写命令。
  • 使用伪客户端执行被读出的写命令。
  • 一直执行步骤 2 和步骤3,直到 AOF 文件中的全部写命令都被处理完毕为止。

当完成以上步骤以后,AOF 文件所保存的数据库状态就会被完整还原出来。

AOF 重写

由于 AOF 持久化是经过保存被执行的写命令来记录 Redis 状态的,因此随着 Redis 长时间运行,AOF 文件中的内容会愈来愈多,文件的体积也会愈来愈大,若是不加以控制的话,体积过大的 AOF 文件极可能对 Redis 甚至宿主计算机形成影响。

为了解决 AOF 文件体积膨胀的问题,Redis 提供了 AOF 文件重写( rewrite) 功能。经过该功能,Redis 能够建立一个新的 AOF 文件来替代现有的 AOF 文件。新旧两个 AOF 文件所保存的 Redis 状态相同,可是新的 AOF 文件不会包含任何浪费空间的荣誉命令,因此新 AOF 文件的体积一般比旧 AOF 文件的体积要小得不少。

示意图

如上图所示,重写前要记录名为list的键的状态,AOF 文件要保存五条命令,而重写后,则只须要保存一条命令。

AOF 文件重写并不须要对现有的 AOF 文件进行任何读取、分析或者写入操做,而是经过读取服务器当前的数据库状态来实现的。首先从数据库中读取键如今的值,而后用一条命令去记录键值对,代替以前记录这个键值对的多条命令,这就是 AOF 重写功能的实现原理。

在实际过程当中,为了不在执行命令时形成客户端输入缓冲区溢出,AOF 重写在处理列表、哈希表、集合和有序集合这四种可能会带有多个元素的键时,会先检查键所包含的元素数量,若是数量超过 REDIS_AOF_REWRITE_ITEMS_PER_CMD ( 通常为64 )常量,则使用多条命令记录该键的值,而不是一条命令。

rewrite的触发机制主要有一下三个:

  • 手动调用 bgrewriteaof 命令,若是当前有正在运行的 rewrite 子进程,则本次rewrite 会推迟执行,不然,直接触发一次 rewrite。
  • 经过配置指令手动开启 AOF 功能,若是没有 RDB 子进程的状况下,会触发一次 rewrite,将当前数据库中的数据写入 rewrite 文件。
  • 在 Redis 定时器中,若是有须要退出执行的 rewrite 而且没有正在运行的 RDB 或者 rewrite 子进程时,触发一次或者 AOF 文件大小已经到达配置的 rewrite 条件也会自动触发一次。

AOF 后台重写

AOF 重写函数会进行大量的写入操做,调用该函数的线程将被长时间阻塞,因此 Redis 在子进程中执行 AOF 重写操做。

  • 子进程进行 AOF 重写期间,Redis 进程能够继续处理客户端命令请求。
  • 子进程带有父进程的内存数据拷贝副本,在不适用锁的状况下,也能够保证数据的安全性。

可是,在子进程进行 AOF 重启期间,Redis接收客户端命令,会对现有数据库状态进行修改,从而致使数据当前状态和 重写后的 AOF 文件所保存的数据库状态不一致。

为此,Redis 设置了一个 AOF 重写缓冲区,这个缓冲区在服务器建立子进程以后开始使用,当 Redis 执行完一个写命令以后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区。

示意图

当子进程完成 AOF 重写工做以后,它会向父进程发送一个信号,父进程在接收到该信号以后,会调用一个信号处理函数,并执行如下工做:

  • 将 AOF 重写缓冲区中的全部内容写入到新的 AOF 文件中,保证新 AOF 文件保存的数据库状态和服务器当前状态一致。
  • 对新的 AOF 文件进行更名,原子地覆盖现有 AOF 文件,完成新旧文件的替换
  • 继续处理客户端请求命令。

在整个 AOF 后台重写过程当中,只有信号处理函数执行时会对 Redis 主进程形成阻塞,在其余时候,AOF 后台重写都不会阻塞主进程。

示意图

后记

后续将会继续学习 Redis 复制和集群相关的知识,但愿你们持久关注。

我的博客地址: remcarpediem