AOF持久化

AOF持久化

        Redis提供了两种持久化功能,RDB和AOF,RDB持久化通过保存数据库中的键值对来记录数据库,AOF持久化时通过保存Redis服务器所执行的写命令来记录数据库状态的,上篇文章已经介绍过RDB持久化,这篇文章就来介绍以下AOF持久化。

举个例子,如果我们对空白数据库执行以下写命令,那么数据库将包含三个键值对:

AOF持久化

        RDB是将msg,fruits,nembers三个键的键值对保存到RDB文件中,而AOF是将服务器执行的SET,SADD,RPUSH三个命令保存到AOF文件中。下面就是执行上面三个命令之后保存的AOF文件:

AOF持久化

        第一句用来指定数据库,下面三个就是我们执行的命令。


AOF持久化的实现

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

命令追加

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

AOF持久化

        比如客户端向服务器发送以下命令:

AOF持久化

        则服务器在执行完SET命令之后,会将以下协议内容追加到aof_buf缓冲区的末尾

AOF持久化

        每执行一个命令,就会将这个命令对应的协议内容追加到aof_buf缓冲区的末尾,这就是AOF持久化命令追加步骤的实现原理。


AOF文件的写入与同步

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

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

AOF持久化

        flushAppendOnlyFile函数的行为由服务器配置的appendfsync选项的值来决定

AOF持久化

        如果用户没有注定为appendfsync选项设置值,则默认使用everysec。

        为了提高文件的写入效率,在现代操作系统中,当用户调用write函数将一些数据写入到文件的时候,操作系统通常会将写入数据暂时保存在一个内从缓冲区里,等到缓冲区空间被填满或者超过指定时限之后才将缓冲区数据写入磁盘。

        这种做法虽然提高了效率,但是有可能发生数据的丢失。

        因此,系统提供了fsync和fdatasync两个同步函数,将缓冲区的数据立即写入磁盘,确保数据的安全。

AOF持久化

AOF持久化


AOF文件的载入和数据还原

        由于AOF文件保存了全部的写命令,所以只要将AOF文件重新执行一遍,就可以还原数据库关闭之前的数据库状态。

详细步骤如下图:

AOF持久化

AOF重写

        由于AOF持久化时通过保存被执行的写命令来记录数据库状态的,所以随着服务器运行时间的增加,AOF文件中的内容会越来越多,文件体积也会越来越大,还原所需要的时间也会越来越多。

AOF持久化

        光记录这个list状态就会保存六条命令。

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


AOF文件重写的实现

        AOF文件重写是通过读取服务器当前状态来实现的。

        比如对上面的例子,AOF文件就不会保存六条指令,而是使用RPUSH list ''C'' ''D'' ''E'' ''F'' ''G''来替代。

        首先从数据库读取现在的值,而安后使用一条命令区记录键值对,代替之前记录这个键值对的多条命令,这就是AOF重写功能的实现原理。

        实际中,当一次性要执行的元素数量超过64时,比如一个集合键包含了超过64个元素,会使用多条SADD命令记录这个集合。


AOF后台重写

        类似于RDB的BGSAVE命令和SAVE命令,aof_rewrite会阻塞当前线程(Redis服务器使用单个线程来处理命令请求)

        所以Redis将AOF重写程序放进子进程里执行,这样就可以同时达到两个目的:

        1.服务器进程(父进程)不会阻塞, 会继续处理命令请求。

        2.子进程带有服务器进程的副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。


        但是这样可能会产生数据不一致,当AOF后台重写进行时,父进程可能会对一些键做一些改变,导致AOF文件和现在的数据库状态不一致。

        为了解决这种数据不一致问题,Redis服务器设置了一个AOF重写缓冲区,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区。

AOF持久化

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

        这个函数会将AOF重写缓冲区的所有内容写入到新AOF文件中,这时新AOF文件保存的数据库状态就和服务器一样了,之后会将新的AOF文件改名,原子地覆盖现有的AOF文件,完成新旧两个AOF文件的替换。

        这个信号处理函数会阻塞当前的命令,当这个函数执行完之后,父进程就可以正常接受命令请求了。

        在整个AOF后台重写过程中,只有信号处理函数执行时会对服务器进程造成阻塞,这将AOF重写对服务器性能造成的影响降到了最低。



重点回顾:
AOF持久化