除了RDB持久化功能之外,Redis还提供了AOF(Append Only File)持久化功能(记录保存数据库状态)。
与RD持久化通过保存数据库中的键值对来记录数据库状态不同,AOF是通过保存Redis服务器所执行的写命令来记录数据库状态的。
如图所示:
举例:在redis客户端执行一下三条命令
RDB持久化保存数据库状态的方法是将msg、fruits、numbers三个键值对保存到RDB文件当中,而AOF持久化保存数据库状态的方法则是将服务器执行的SET 、SADD 、RPUSH三个命令保存到AOF文件中去。
服务器启动时,可以通过载入和执行AOF文件中保存的命令来还原服务器关闭前的数据库状态,以下是服务器载入AOF文件并还原数据库状态时打印的日志:
[8321] 05 Sep 11:58:50.448 # Server started,Redisversion 2.9.11 [8321] 05 Sep 11:58:50.448 * DB loaded from append only file: 0.000 seconds [8321] 05 Sep 11:58:50.448 * The server is now ready to accept connections on port 6379
11.1 持久化功能的实现,AOF文件写入、保存、载入操作的实现原理
AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤;
11.1.1 命令追加
当AOF持久化功能处于打开状态时,服务器执行完一个写命令之后,会以协议的格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾。
1 struct redisServer { 2 //... 3 //AOF缓冲区 4 sds aof_buf; 5 // 6 }
11.1.2 AOF文件的写入与同步
Redis的服务器进程就是一个时间循环(loop),这个循环中的文件事件负责接收客户端的命令请求,以及想客户端发送命令回复;而时间事件则负责执行像serverCron函数这样需要定时执行的函数;
因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf 缓冲区,所以服务器在每次结束一个事件循环之前,都会调用 flushAppendOnlyFile 函数,考虑是否将 aof_buf 缓冲区的内容写入和保存到 AOF 文件里面;
flushAppendOnlyFile 函数的行为是有服务器配置的 appendfsync 选项的值来决定,各个不同的值产生的行为如下表所示:
appendfsync 选项的值 | flushAppendOnlyFile 函数的行为 | 效率与安全性 |
always | 每次事件循环都要将 aof_buf 缓冲区的所有内容写入并同步到 AOF 文件; | 写入最慢;最安全(最多丢失一个事件循环中所所产生的命令数据) |
everysec(默认值) | 每次事件循环都要将 aof_buf 缓冲区的所有内容写入AOF文件;每隔一秒钟在子线程中都要对 AOF 文件进行同步,并且这个同步操作是由2一个线程专门负责的; | 写入足够快,并且计算出现故障停机,数据库最多丢失一秒钟的命令数据; |
no | 每次事件循环都要将 aof_buf 缓冲区的所有内容写入AOF文件;何时对 AOF 进行文件进行同步由操作系统决定; | 写入最快;同步时长最长,并且计算出现故障停机,服务器丢失上次同步 AOF 文件之后的所有写命令数据l; |
操作系统——文件的吸入与同步 |
为了提高文件的写入效率,现在操作系统中,当用户调用write函数,将一些数据写入到文件时,OS通常会将写入的数据暂时保存在一个内存缓存区里面,等缓冲区的空间被填满,或者超过指定的时限之后,才真正地将缓冲区数据写入到磁盘当中; 这种做法虽然提高了效率,也为数据写入带来了安全问题,一位如果计算机发送停机故障,那么保存在内存缓冲区的写入数据将会丢失; 为此,OS提供了 fsync 和 fdatasync 两个同步函数,它们可以强制让操作系统立即将缓冲区的数据写入到硬盘当中,从而确保数据写入的安全性; |
11.2 AOF 文件的载入与还原
因为 AOF 文件里面包含了重建数据库状态所需要的所有写命令,所以服务器只要读入并重新执行一遍 AOF 文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态;
Redis 读取 AOF 文件并还原数据库状态的详细步骤:
11.3 AOF 重写
随着时间的流逝,AOF 文件中的内容越来越多,文件体积越来越大,如果不加以控制,体积过大的 AOF 文件很可能对 Redis 服务区、甚至整个宿主计算机造成影响,并且 AOF 文件的体积越大,使用 AOF 文件对数据进行还原所需要的时间越多。
为了解决 AOF 体积膨胀问题,Redis提供了 AOF 文件重写(rewrite)功能:Redis服务器通过创建一个新的 AOF 文件替代现有的 AOF 文件,新旧两个文件保存的数据库状态相同,但是新的 AOF 文件不会保存任何浪费空间的冗余命令;
实现原理:该功能并不需要对现有的 AOF 文件进行任何读取、分许或者写入操作;而是通过读取服务器当前数据库状态来实现的。
AOF 后台重写:AOF 重写程序 aof_rewrite 函数可以很好的完成创建一个新 AOF 文件的任务,但是因为该函数会进行大量的写入操作,所以调用此函数的线程将会长时间被阻塞;
因为Redis服务器使用单线程处理命令请求,所以如果由服务器直接调用 aof_rewrite 函数。那么在重写期间,服务器将无法处理客户端发送来的命令请求;
所以Redis决定将AOF重写程序放入到子进程中去执行;
使用子进程所带来的问题:
新的客户端命令请求会修改当前数据库的状态,引发服务器当前数据库状态与重写后 AOF 文件保存的数据库状态不一致!
为了解决这个问题,Redis服务器设置类以一个 AOF 重写缓冲区,该缓冲区是在服务器创建子进程之后开始使用,当服务器执行完一个写命令之后,它会同时将这个写命令发送给 AOF 缓冲区和 AOF 重写缓冲区,如图所示:
在子进程执行 AOF 重写期间,服务器进程需要执行一下三个工作:
(1)执行客户端发送得命令;
(2)将执行后得写命令追缴到 AOF 缓冲区;
(3)将执行后得写命追加到 AOF 重写缓冲区;
当子进程完成 AOF 重写工作之后,它会给父进程发送一个信号,父进程在接受到信号之后,会调用一个信号处理函数,并执行以下工作:
(1)将 AOF 重写缓冲区得所有内容写入到新 AOF 文件中,这时,新AOF文件所保存的数据库状态和服务器当前数据库状态保持一致;
(2)对新 AOF 文件改名:原子地覆盖现有 AOF 文件,完成新旧两个 AOF 文件的替换;
信号处理函数执行完成之后,父进程就可以正常处理接受命令请求了;
在整个 AOF 后台重写过程中,只有信号处理函数会对服务器进程(父进程)造成阻塞,在其它时候,AOF 后台重写都不会阻塞父进程,这将 AOF 重写对服务器的性能造成的影响降到了最低;