Redis持久化之AOF

AOF(Append Only File):增量持久化方式
与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的,如下图:
图片.png

被写入AOF文件的所有命令都是以Redis的命令请求协议格式(RESP协议REdis Serialization Protocol)保存的,RESP2是纯文本格式。

1、AOF持久化的实现

AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
(1)命令追加
当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以RESP协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾:

struct redisServer {
    sds aof_buf; // 使用sds数据结构实现AOF缓冲区
}

命令追加在执行完命令之后,表明AOF是写后日志,采用写后日志的好处:

I) 为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。
而写后日志这种方式,就是先让系统执行命令,只有命令能执行成功,才会被记录到日志中,否则,系统就会直接向客户端报错。所以,Redis 使用写后日志这一方式的一大好处是,可以避免出现记录错误命令的情况。

II) 除此之外,AOF 还有一个好处:它是在命令执行后才记录日志,所以不会阻塞当前的写操作。但会阻塞之后的写操作。

(2)AOF文件的写入与同步
Redis的服务器主进程就是一个事件循环:
Redis持久化之AOF_第1张图片

Redis的命令追加在主循环(aeMain函数)中的每次处理完写命令的执行后,通过propagate函数触发。

propagate方法将当前命令的内容append到redisServer对象的aof_buf变量中。主循环在下一个迭代进入多路复用的select方法前,Redis会通过flushAppendOnlyFile方法将aof_buf的内容write到AOF对应的文件中。但write操作只是将数据写到缓存中,什么时候从缓存真正落地到磁盘上,取决于操作系统。只有显示调用fsync()方法才能强制地让操作系统落地数据到磁盘。

AOF写入与同步的语义
写入AOF文件指的是:调用write()写到操作系统缓存
同步AOF文件指的是:调用fsync()强制缓存落地到磁盘文件

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

  • always:主循环的每个迭代的flushAppendOnlyFile函数中都要将aof_buf缓冲区中的所有内容写入到AOF文件,并且直接触发fsync方法同步AOF文件,强制数据落地到磁盘。由于每个命令都在写入磁盘后才返回,故容错能力最高,即使出现故障宕机,也只会丢失一个事件循环中所产生的命令数据。
  • everysec:服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,并且每隔一秒异步地触发一次fsync方法。fsync方法的执行者是bio线程池中的某个线程。flushAppendOnlyFile函数只是作为生产者将fsync任务放入队列,由bio线程消费并执行
  • no:将aof_buf缓冲区中的所有内容写入到AOF文件,但不显示调用fsync,由操作系统决定什么时候落地磁盘。这种模式下,Redis无法决定增量的落地时间,因此容错能力不可控。

综上,以上3种策略将aof_buf缓冲区中的所有内容写入到AOF文件的时机都一样,不同的是fsync强制落盘的时机。由名字可以看出,appendfsync是同步AOF文件策略。

2、AOF文件的载入与数据还原

Redis会创建一个不带网络连接的伪客户端(fake client):因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令。

3、AOF重写

(1)为了解决AOF文件体积膨胀的问题

AOF 文件过大会带来性能问题。这里的“性能问题”,主要在于以下三个方面:

  • 一是,文件系统本身对文件大小有限制,无法保存过大的文件;
  • 二是,如果文件太大,之后再往里面追加命令记录的话,效率也会变低;
  • 三是,如果发生宕机,AOF 中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到 Redis 的正常使用。

虽然Redis将生成新的AOF文件替换旧AOF文件的功能命名为“AOF文件重写”,但实际上,AOF文件重写并不需要对现有的AOF文件进行任何读取、分析或者写入操作,这个功能是通过读取服务器当前的数据器状态来实现的。因为生成的新AOF文件只包含还原当前数据库状态所必须的命令,所以新AOF文件不会浪费任何硬盘空间。

(2)作为一种辅佐性的维护手段,Redis不希望AOF重写造成服务器无法处理请求,所以Redis决定将AOF重写程序放到子进程里执行,这样做可以同时达到两个目的:

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

(3)由于子进程在进行AOF重写期间,服务器进程还需要继续处理命令请求,这就可能导致重写后的AOF文件和服务器的当前数据库状态不一致。为了解决此问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区,如下图:
Redis持久化之AOF_第2张图片

这也就是说,在子进程执行AOF重写期间,服务器进程需要执行以下三个工作:

  • 执行客户端发来的命令
  • 将执行后的写命令追加到AOF缓冲区
  • 将执行后的写命令追加到AOF重写缓冲区

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

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

  • 将AOF重写缓冲区中的所有内容写入到新AOF文件中,这时新AOF文件所保存的数据库状态将和服务器当前的数据库状态一致。
  • 对新的AOF文件进行改名,原子地覆盖现有的AOF文件,完成新旧两个AOF文件的替换。

4、何时触发AOF重写?

  1. 手动发送“bgrewriteaof”指令;
  2. 有两个配置项在控制AOF重写的触发时机:
    1)auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
    2)auto-aof-rewrite-percentage: 这个值的计算方法是:当前AOF文件大小和上一次重写后AOF文件大小的差值,再除以上一次重写后AOF文件大小。也就是当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。
    AOF文件大小同时超出上面这两个配置项时,会触发AOF重写。
参考资料
1、redis设计与实现
2、redis深度历险

你可能感兴趣的:(redis)