Redis AOF,一篇即懂

序章

除了RDB持久化功能以外,Redis还提供了AOF(Append Only File)持久化功能。与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis所执行的写命令来记录数据库状态的。

AOF如何打开

redis默认情况是关闭AOF的,所以要使用就要先通过以下方式打开:
第一种方式:
打开 redis.conf  修改以下参数:

appendonly  yes        (默认no,关闭)表示是否开启AOF持久化: 

appendfilename “appendonly.aof”   AOF持久化配置文件的名称:**

默认情况下redis安装目录会生成 appendonly.aof文件,如果没有则执行以下方式
第二种方式:
在cmd通过redis-cli连接到服务器的命令界面里
输入
config set appendonly yes
config set save “”(可选)

执行的第一条命令开启了 AOF 功能: Redis 会阻塞直到初始 AOF 文件创建完成为止, 之后 Redis 会继续处理命令请求, 并开始将写入命令追加到 AOF 文件末尾。

执行的第二条命令用于关闭 RDB 功能。 这一步是可选的, 如果你愿意的话, 也可以同时使用 RDB 和 AOF 这两种持久化功能。(如果RDB和AOF同时开启,则AOF优先加载)

AOF如何实现持久化

AOF持久化功能的实现可以分为命令追加、文件写入、文件同步三个步骤。

命令追加:

当AOF持久化功能打开时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。

AOF文件的写入与同步:

每当服务器常规任务函数被执行、 或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作:

WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。

SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。

两个步骤都需要根据一定的条件来执行, 而这些条件由 AOF 所使用的保存模式来决定, 以下小节就来介绍 AOF 所使用的三种保存模式, 以及在这些模式下, 步骤 WRITE 和 SAVE 的调用条件。

Redis 目前支持三种 AOF 保存模式,它们分别是:

AOF_FSYNC_NO :不保存。

AOF_FSYNC_EVERYSEC :每一秒钟保存一次。

AOF_FSYNC_ALWAYS :每执行一个命令保存一次。

不保存(AOF_FSYNC_NO)

在这种模式下, 每次调用 flushAppendOnlyFile 函数, WRITE 都会被执行, 但 SAVE 会被略过。

在这种模式下, SAVE 只会在以下任意一种情况中被执行:

Redis 被关闭

AOF 功能被关闭

系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)

这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。

每一秒钟保存一次(AOF_FSYNC_EVERYSEC)

在这种模式中, SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程调用的, 所以它不会引起服务器主进程阻塞。

注意, 在上一句的说明里面使用了词语“原则上”, 在实际运行中, 程序在这种模式下对 fsync 或 fdatasync 的调用并不是每秒一次, 它和调用 flushAppendOnlyFile 函数时 Redis 所处的状态有关。

每当 flushAppendOnlyFile 函数被调用时, 可能会出现以下四种情况:

子线程正在执行 SAVE ,并且:

这个 SAVE 的执行时间未超过 2 秒,那么程序直接返回,并不执行 WRITE 或新的 SAVE 。

这个 SAVE 已经执行超过 2 秒,那么程序执行 WRITE ,但不执行新的 SAVE 。注意,因为这时 WRITE 的写入必须等待子线程先完成(旧的) SAVE ,因此这里 WRITE 会比平时阻塞更长时间。

子线程没有在执行 SAVE ,并且:

上次成功执行 SAVE 距今不超过 1 秒,那么程序执行 WRITE ,但不执行 SAVE 。

上次成功执行 SAVE 距今已经超过 1 秒,那么程序执行 WRITE 和 SAVE 。

可以用流程图表示这四种情况:

image.png

根据以上说明可以知道, 在“每一秒钟保存一次”模式下, 如果在情况 1 中发生故障停机, 那么用户最多损失小于 2 秒内所产生的所有数据。

如果在情况 2 中发生故障停机, 那么用户损失的数据是可以超过 2 秒的。

Redis 官网上所说的, AOF 在“每一秒钟保存一次”时发生故障, 只丢失 1 秒钟数据的说法, 实际上并不准确。

每执行一个命令保存一次(AOF_FSYNC_ALWAYS)

在这种模式下,每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。

另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令请求。

AOF 保存模式对性能和安全性的影响

在上一个小节, 我们简短地描述了三种 AOF 保存模式的工作方式, 现在, 是时候研究一下这三个模式在安全性和性能方面的区别了。

对于三种 AOF 保存模式, 它们对服务器主进程的阻塞情况如下:

不保存(AOF_FSYNC_NO):写入和保存都由主进程执行,两个操作都会阻塞主进程。

每一秒钟保存一次(AOF_FSYNC_EVERYSEC):写入操作由主进程执行,阻塞主进程。保存操作由子线程执行,不直接阻塞主进程,但保存操作完成的快慢会影响写入操作的阻塞时长。

每执行一个命令保存一次(AOF_FSYNC_ALWAYS):和模式 1 一样。

因为阻塞操作会让 Redis 主进程无法持续处理请求, 所以一般说来, 阻塞操作执行得越少、完成得越快, Redis 的性能就越好。

模式 1 的保存操作只会在AOF 关闭或 Redis 关闭时执行, 或者由操作系统触发, 在一般情况下, 这种模式只需要为写入阻塞, 因此它的写入性能要比后面两种模式要高, 当然, 这种性能的提高是以降低安全性为代价的: 在这种模式下, 如果运行的中途发生停机, 那么丢失数据的数量由操作系统的缓存冲洗策略决定。

模式 2 在性能方面要优于模式 3 , 并且在通常情况下, 这种模式最多丢失不多于 2 秒的数据, 所以它的安全性要高于模式 1 , 这是一种兼顾性能和安全性的保存方案。

模式 3 的安全性是最高的, 但性能也是最差的, 因为服务器必须阻塞直到命令信息被写入并保存到磁盘之后, 才能继续处理请求。

综合起来,三种 AOF 模式的操作特性可以总结如下:

image.png

AOF 文件的读取和数据还原

AOF 文件保存了 Redis 的数据库状态, 而文件里面包含的都是符合 Redis 通讯协议格式的命令文本。

这也就是说, 只要根据 AOF 文件里的协议, 重新执行一遍里面指示的所有命令, 就可以还原 Redis 的数据库状态了。

Redis 读取 AOF 文件并还原数据库的详细步骤如下:

1.创建一个不带网络连接的伪客户端(fake client)。

2.读取 AOF 所保存的文本,并根据内容还原出命令、命令的参数以及命令的个数。

3.根据命令、命令的参数和命令的个数,使用伪客户端执行该命令。

4.执行 2 和 3 ,直到 AOF 文件中的所有命令执行完毕。

完成第 4 步之后, AOF 文件所保存的数据库就会被完整地还原出来。

注意, 因为 Redis 的命令只能在客户端的上下文中被执行, 而 AOF 还原时所使用的命令来自于 AOF 文件, 而不是网络, 所以程序使用了一个没有网络连接的伪客户端来执行命令。 伪客户端执行命令的效果, 和带网络连接的客户端执行命令的效果, 完全一样。

AOF重写

因为AOF持久化是通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的流逝,AOF文件中的内容越来越多,文件体积越来越大,如果不加以控制,会对redis服务器甚至宿主计算器造成影响。

所谓的“重写”其实是一个有歧义的词语, 实际上, AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取, 它针对的是数据库中键的当前值。

考虑这样一个情况, 如果服务器对键 list 执行了以下四条命令:

RPUSH list 1 2 3 4// [1, 2, 3, 4]

RPOP list // [1, 2, 3]

LPOP list // [2, 3]

LPUSH list 1 // [1, 2, 3]

那么当前列表键 list 在数据库中的值就为 [1, 2, 3] 。

如果我们要保存这个列表的当前状态, 并且尽量减少所使用的命令数, 那么最简单的方式不是去 AOF 文件上分析前面执行的四条命令, 而是直接读取 list 键在数据库的当前值, 然后用一条 RPUSH 1 2 3 命令来代替前面的四条命令。

再考虑这样一个例子, 如果服务器对集合键 animal 执行了以下命令:

SADD animal cat// {cat}

SADD animal dog panda tiger// {cat, dog, panda, tiger}

SREManimal cat// {dog, panda, tiger}

SADD animal cat lion// {cat, lion, dog, panda, tiger}

那么使用一条 SADD animal cat lion dog panda tiger 命令, 就可以还原 animal 集合的状态, 这比之前的四条命令调用要大大减少。

除了列表和集合之外, 字符串、有序集、哈希表等键也可以用类似的方法来保存状态, 并且保存这些状态所使用的命令数量, 比起之前建立这些键的状态所使用命令的数量要大大减少。

AOF重写程序aof_rewrite函数可以很好完成创建一个新AOF文件的任务,但是这个函数会进行大量写入操作,会长时间阻塞,所以Redis将AOF重写程序放到子进程里执行,这样做达到两个目的:

·子进程AOF重写期间,服务器进程可以继续处理命令请求。

·子进程带有数据库进程的数据副本,使用子进程而不是线程,可以避免使用锁的情况下保证数据安全。

不过,使用子进程也有一个问题需要解决,就是AOF重写期间如果有新的写命令进来,不能漏掉,那样会数据不一致。

于是Redis服务器设置了一个AOF重写缓冲区

最后流程变为:

1.执行客户端发来的命令

2.将执行的写命令追加到AOF缓冲区

3.将执行后的写命令追加到AOF重写缓冲区

如上图。

这样一来可以保证:

·AOF缓冲区的内容会定期被写入和同步到AOF文件,对现有AOF文件的处理工作会照常进行。

·从创建子进程开始,服务器执行的所有写命令会被记录到AOF重写缓冲区里面

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

1.将AOF重写缓冲区中的所有内容写入新的AOF文件中,这时新AOF文件锁保存的数据库状态和服务器当前状态一致

2.对新的AOF文件进行改名,原子性操作地覆盖现有的AOF文件,完成新旧AOF文件的替换。

这个信号函数执行完毕以后,父进程就可以继续像往常一样接受命令请求了,在整个AOF后台重写过程中,只有信号处理函数执行时会对服务器进程造成阻塞,其他时候都可以继续处理请求,这样AOF重写对服务器性能造成的影响降到了最低。

以上就是AOF后台重写,也即是BGREWRITEAOF命令的实现原理。

AOF的缺点

·体积大:对于相同的数据集来说,AOF文件的体积通常要大于 RDB文件的体积。

·性能差:根据所使用的Fsync策略,AOF的速度可能会慢于 RDB。在一般情况下,每秒Fsync的性能依然非常高,而关闭 Fsync可以让 AOF的速度和 RDB一样快,即使在高负荷之下也是如此。不过在处理巨大的写入载入时,RDB可以提供更有保证的最大延迟时间(Latency)。

转载:https://www.jianshu.com/p/1e3...

阅读 152

推荐阅读
目录