Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

redis基础学习之AOF

2017-09-22

AOF持久化

aof 持久化是通过保存redis服务器所执行的写命令来记录数据库状态的

  • RDB 将数据库的快照(snapshot)以二进制的方式保存到磁盘中。
  • AOF 则以协议文本的方式,将所有对数据库进行过写入的命令(及其参数)记录到 AOF 文件,以此达到记录数据库状态的目的。

rdb持久化保存数据库状态的方法是将键值对保存到RDB文件中,而AOF持久化保存数据库状态的方法是将服务器执行的命令保存到文件中.

被写入AOF文件的所有命令都是以redis的命令请求协议格式保存的,因为redis命令请求协议是纯文本格式,所以我们可以直接打开一个AOF文件,观察里面的内容.

服务器在启动时,可以通过载入和执行AOF文件中保存的命令来还原服务器关闭之前的数据库状态,以下就是服务器载入AOF文件并还原数据库状态时打印的日志.

AOF持久化的实现

AOF持久化功能的实现可分为命令追加(append),文件写入,文件同步(sync)三个步骤.

  • 命令追加

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


struct redisServer{
	//AOF缓冲区

	sds aof_buf;
};


  • AOF文件的写入与同步

Redis的服务器进程就是一个事件循环,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行像serverCron函数这样需要定时运行的函数.

因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到 aof_buf缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面.

每当服务器常规任务函数被执行、 或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作:
  • WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。
  • SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
def eventLoop():

 while true:

 //处理文件事件,接收,命令请求以及发送命令回复
 //处理命令请求时可能会有新内容追缴到aof_buf缓冲区中

 processFileEvents()

 //处理时间事件
 processTimeEvents()


 //考虑是否将aof_buf中的内容写入和保存到AOF文件里面
 flushAppendOnlyFile()


flushAppendOnlyFile函数的行为有服务器配置的appendfsync选项的值来决定,各个不同值产生的行为如下:

appendfsync选项的值 flushAppendOnlyFile函数的行为 影响
always 将aof_buf缓冲区中的所有内容写入并同步到AOF文件 写入和保存都由主进程执行,两个操作都会阻塞主进程。
everysec 将aof_buf缓冲区中的所有内容写入到AOF文件,如果上次同步AOF文件的时间距离现在超过一秒钟,那么在此对AOF文件进行同步时,并且这个同步操作时又一个线程专门负责执行 写入操作由主进程执行,阻塞主进程。保存操作由子线程执行,不直接阻塞主进程,但保存操作完成的快慢会影响写入操作的阻塞时长。
no 将aof_buf缓冲区中的所有内容写入到AOF文件,但并不对AOF文件进行同步,何时同步由操作系统来决定 写入和保存都由主进程执行,两个操作都会阻塞主进程。

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

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

** 默认为everysec **

(1) always : 在这种模式下,每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令请求。

(2) everysec: SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程调用的, 所以它不会引起服务器主进程阻塞。注意, 在上一句的说明里面使用了词语“原则上”, 在实际运行中, 程序在这种模式下对 fsync 或 fdatasync 的调用并不是每秒一次, 它和调用 flushAppendOnlyFile 函数时 Redis 所处的状态有关。

image

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

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

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

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

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

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

模式 WRITE 是否阻塞? SAVE 是否阻塞? 停机时丢失的数据量
AOF_FSYNC_NO 阻塞 阻塞 操作系统最后一次对 AOF 文件触发 SAVE 操作之后的数据。
AOF_FSYNC_EVERYSEC 阻塞 不阻塞 一般情况下不超过 2 秒钟的数据。
AOF_FSYNC_ALWAYS 阻塞 阻塞 最多只丢失一个命令的数据。

AOF文件的载入与数据还原

aof文件包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态.

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

  • 创建一个不带网络连接的伪客户端:因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于aof文件而不是网络连接,所以服务器使用一个没有网络连接的伪客户端来执行AOF文件保存的写命令,伪客户端执行命令效果和带网络客户端执行命令的效果完全一样.

  • 从aof文件中分析并读取出一条写命令

  • 使用伪客户端执行被读出的写命令

  • 一直执行步骤2和步骤3,直到AOF文件中所有写命令都被处理完毕为止

为了避免对数据的完整性产生影响, 在服务器载入数据的过程中, 只有和数据库无关的订阅与发布功能可以正常使用, 其他命令一律返回错误。

AOF重写

虽然Redis将生成新的AOF文件替换旧AOF文件的功能命名为’AOF文件重写’,但实际上,AOF文件重写并不需要对现有的AOF文件进行任何读取,分析或者写入操作,这个功能时通过读取服务器当前的数据库状态来实现的.

为了解决AOF文件体积膨胀的问题,redis提供了AOF文件重写功能,通过该功能,redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件保存的数据库状态相同,但新的AOF文件不会包含任何良妃空间冗余命令,所以新的Aof文件体积通常会比旧的AOF文件体积小的多.

根据键的类型, 使用适当的写入命令来重现键的当前值, 这就是 AOF 重写的实现原理。

AOF 后台重写

AOF 重写程序可以很好地完成创建一个新 AOF 文件的任务, 但是, 在执行这个程序的时候, 调用者线程会被阻塞。很明显, 作为一种辅佐性的维护手段, Redis 不希望 AOF 重写造成服务器无法处理请求, 所以 Redis 决定将 AOF 重写程序放到(后台)子进程里执行, 这样处理的最大好处是:

  1. 子进程进行 AOF 重写期间,主进程可以继续处理命令请求。
  2. 子进程带有主进程的数据副本,使用子进程而不是线程,可以在避免锁的情况下,保证数据的安全性。

不过, 使用子进程也有一个问题需要解决: 因为子进程在进行 AOF 重写期间, 主进程还需要继续处理命令, 而新的命令可能对现有的数据进行修改, 这会让当前数据库的数据和重写后的 AOF 文件中的数据不一致。

为了解决这个问题, Redis 增加了一个 AOF 重写缓存, 这个缓存在 fork 出子进程之后开始启用, Redis 主进程在接到新的写命令之后, 除了会将这个写命令的协议内容追加到现有的 AOF 文件之外, 还会追加到这个缓存中

换言之, 当子进程在执行 AOF 重写时, 主进程需要执行以下三个工作:

  • 处理命令请求。
  • 将写命令追加到现有的 AOF 文件中。
  • 将写命令追加到 AOF 重写缓存中。

这样一来可以保证:

  • 现有的 AOF 功能会继续执行,即使在 AOF 重写期间发生停机,也不会有任何数据丢失。
  • 所有对数据库进行修改的命令都会被记录到 AOF 重写缓存中。

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

  • 将 AOF 重写缓存中的内容全部写入到新 AOF 文件中。
  • 对新的 AOF 文件进行改名,覆盖原有的 AOF 文件。

当步骤 1 执行完毕之后, 现有 AOF 文件、新 AOF 文件和数据库三者的状态就完全一致了。

当步骤 2 执行完毕之后, 程序就完成了新旧两个 AOF 文件的交替。

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

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

重写触发条件

每次当 serverCron 函数执行时, 它都会检查以下条件是否全部满足, 如果是的话, 就会触发自动的 AOF 重写:

  • 没有 BGSAVE 命令在进行。
  • 没有 BGREWRITEAOF 在进行。
  • 当前 AOF 文件大小大于 server.aof_rewrite_min_size (默认值为 1 MB)。
  • 当前 AOF 文件大小和最后一次 AOF 重写后的大小之间的比率大于等于指定的增长百分比。

默认情况下, 增长百分比为 100% , 也即是说, 如果前面三个条件都已经满足, 并且当前 AOF 文件大小比最后一次 AOF 重写时的大小要大一倍的话, 那么触发自动 AOF 重写。

内容来自:«redis设计与实现» redis设计与实现


上一篇 I/O多路复用

评论