Redis 6.0/7.0 AOF重写方案
Redis 6.0/7.0 AOF重写方案

随着数据的不断写入,AOF文件会变得越来越大,为了解决问题,Redis使用了AOF重写机制来解决这个问题。在Redis7.0前后AOF重写方案有了比较大的变化,本文简单的讲解了两种方案的不同之处。
6.0的AOF重写
重写过程
流程说明:
1)开始执行AOF重写请求,如果当前正在执行bgsave保存RDB文件操作,则重写命令会延迟到basave完成之后再执行。
2)父进程执行fork创建子进程,开销等同于bgsave过程
3.1)主进程fork完操作之后,继续响应其他命令,所有修改命令依然写入AOF缓冲区并根据appendfsync策略同步到磁盘,保证原有AOF机制正确
3.2)由于fork操作运用写时复制(Copy-On-Write)技术,子进程只能共享fork操作时的内存数据,此时父进程仍然还在响应命令,redis使用"AOF重写缓冲区"保存这部分新数据,防止新AOF文件生成期间丢失这部分数据。可以通俗的理解生成新AOF文件的过程中产生的增量数据,保存在aof_rewrite_buf里面
4)子进程根据内存快照,按照命令合并规则写入到新的AOF文件,每次批量写入硬盘,默认为32MB写一次,由参数aof-rewrite-incremental-fsync控制,防止单次刷盘过多造成硬盘阻塞。
5.1)新AOF写入完成后,子进程发送信号给父进程,父进程更新统计信息
5.2)父进程把AOF重写缓冲区的数据写入到新的AOF文件
5.3)使用新的AOF文件替换老文件,完成AOF重写。
为了方便理解画了一个流程图。

从图中可以看到新请求不仅写入了AOF缓冲区还写入了AOF重写缓冲区,这是为什么呢?为什么不直接将重写期间保存在AOF缓冲区中的新命令在子进程写完临时文件后直接追加呢?因为AOF重写虽然是为了减少AOF文件的大小,但是**AOF重写是对数据库的重写而不是对AOF文件的重写。**Redis写磁盘的时机与MySQL不同,Redis是先写入内存,再写入磁盘,所以在创建子进程的那一刻,AOF缓冲区中可能还有数据未被写入AOF文件,所以我们需要创建一个新的缓冲区,来记录数据库的变化,这就是AOF重写缓冲区的意义所在。举个例子来说明:
在**T1时刻数据库有<span style="color: blue">"abcd"</span>这些数据,AOF缓冲区中的<span style="color: blue">"cd"</span>还未被写入AOF文件,这个时刻创建了子进程进行AOF重写,然后再T2时刻完成了重写。在T1 - T2期间,主进程往数据库写入了<span style="color: blue">"ef"</span>数据。假设AOF缓冲区的数据还未写入旧的AOF文件,那么在T2**时刻各个缓冲区和文件情况是这样的:旧的AOF文件有<span style="color: blue">"ab"</span>数据;AOF缓冲区有<span style="color: blue">"cdef"</span>数据,新的AOF文件有<span style="color: blue">"abcd"</span>数据;AOF重写缓冲区有<span style="color: blue">"ef"</span>数据。然后再将AOF重写缓冲区的数据追加到新的AOF文件,完成了AOF文件的重写。从这个过程中我们可以看出来AOF重写缓冲区的作用就是记录了重写期间数据库发生的变化。
6.0重写方案弊端
从上述的流程说明和流程图中我们可以看出6.0方案的弊端:
-
占用主进程CPU时间。重写期间需要额外向
AOF重写缓冲区写数据;通过管道给子进程发送AOF重写缓冲区,还有子进程结束后将AOF重写缓冲区数据追加到临时文件中。 -
额外的内存开销。重写期间需要将fork之后的数据变化同时写入
AOF缓冲区和AOF重写缓冲区。这是Redis 6.2.7中开辟缓冲区的代码。c/* This function is called by the child rewriting the AOF file to read * the difference accumulated from the parent into a buffer, that is * concatenated at the end of the rewrite. */ ssize_t aofReadDiffFromParent(void) { char buf[65536]; /* Default pipe buffer size on most Linux systems. */ ssize_t nread, total = 0; while ((nread = read(server.aof_pipe_read_data_from_parent,buf,sizeof(buf))) &gt; 0) { server.aof_child_diff = sdscatlen(server.aof_child_diff,buf,nread); total += nread; } return total; } -
额外的磁盘开销,
AOF缓冲区最终会写入旧的AOF文件,AOF重写缓冲区写入新的AOF文件,但是数据是一样的。
7.0的AOF重写
为了解决6.0AOF重写方案的弊端,Redis7.0引入了MP-AOF方案来解决。
MP-AOF方案概述
MP-AOF,全程Multi Part AOF,就是多部分AOF意思,通俗点讲就是由7.0的AOF文件部分由多个文件组成。MP-AOF文件有两个核心部分:
- BASE AOF文件,基础AOF文件,记录了基本的命令
- INCR AOF文件,记录了在重写过程新增的操作

从图中可以看到,重写期间,主进程只需要写入AOF缓冲区,最终AOF缓冲区的数据最终刷入新打开的AOF-incr.2文件,而子进程只需要根据fork时的数据库的数据重写并生成一个新的AOF-base.2文件,两个文件合起来就代表了当前时刻Redis的所有数据。在写完这两个文件之后通过manifest设置旧文件为history,这些history文件会被Redis异步删除掉。
总的来说在Redis7.0中,重写期间不需要AOF重写缓冲区,也不需要父子进程通过管道传输数据。这样一来,CPU、内存、磁盘的性能损耗会降低很多。
参考资料:
