AOF 重写函数与触发时机
实现 AOF 重写的函数是 rewriteAppendOnlyFileBackground,它是在aof.c文件中实现的。在这个函数中,会调用 fork 函数创建一个 AOF 重写子进程,来实际执行重写操作。
rewriteAppendOnlyFileBackground 函数一共会在三个函数中被调用:
- 第一个是 bgrewriteaofCommand 函数。这个函数是在 aof.c 文件中实现的,对应了我们在 Redis server 上执行 bgrewriteaof 命令,也就是说,我们手动触发了 AOF rewrite 的执行。
不过,即使我们手动执行了 bgrewriteaof 命令,bgrewriteaofCommand 函数也会根据以下两个条件,来判断是否实际执行 AOF 重写。
条件一:当前是否已经有 AOF 重写的子进程正在执行。如果有的话,那么 bgrewriteaofCommand 函数就不再执行 AOF 重写了。
条件二:当前是否有创建 RDB 的子进程正在执行。如果有的话,bgrewriteaofCommand 函数会把全局变量 server 的 aof_rewrite_scheduled 成员变量设置为 1,这个标志表明 Redis server 已经将 AOF 重写设为待调度运行,等后续条件满足时,它就会实际执行 AOF 重写。
只有当前既没有 AOF 重写子进程也没有 RDB 子进程,bgrewriteaofCommand 函数才会立即调用 rewriteAppendOnlyFileBackground 函数,实际执行 AOF 重写。
void bgrewriteaofCommand(client *c) {
if (server.aof_child_pid != -1) {
.. //有AOF重写子进程,因此不执行重写
} else if (server.rdb_child_pid != -1) {
server.aof_rewrite_scheduled = 1; //有RDB子进程,将AOF重写设置为待调度运行
...
} else if (rewriteAppendOnlyFileBackground() == C_OK) { //实际执行AOF重写
...
}
...
}
- 第二个是 startAppendOnly 函数。这个函数也是在 aof.c 文件中实现的,它本身会被 configSetCommand 函数(在config.c文件中)和 restartAOFAfterSYNC 函数(在replication.c文件中)调用。
对于 configSetCommand 函数来说,它对应了我们在 Redis 中执行 config 命令启用 AOF 功能,如下所示:
config set appendonly yes
一旦 AOF 功能启用后,configSetCommand 函数就会调用 startAppendOnly 函数,执行一次 AOF 重写。
对于 restartAOFAfterSYNC 函数来说,它会在主从节点的复制过程中被调用。简单来说,就是当主从节点在进行复制时,如果从节点的 AOF 选项被打开,那么在加载解析 RDB 文件时,AOF 选项就会被关闭。然后,无论从节点是否成功加载了 RDB 文件,restartAOFAfterSYNC 函数都会被调用,用来恢复被关闭的 AOF 功能。
startAppendOnly 函数也会判断当前是否有 RDB 子进程在执行,如果有的话,它会将 AOF 重写设置为待调度执行。除此之外,如果 startAppendOnly 函数检测到有 AOF 重写子进程在执行,那么它就会把该子进程先 kill 掉,然后再调用 rewriteAppendOnlyFileBackground 函数进行 AOF 重写。
- 第三个是 serverCron 函数。
- 首先,serverCron 函数会检测当前是否没有 RDB 子进程和 AOF 重写子进程在执行,并检测是否有 AOF 重写操作被设置为了待调度执行,也就是 aof_rewrite_scheduled 变量值为 1。serverCron 函数默认会每 100 毫秒执行并检测这个变量值。
如果这三个条件都满足,那么 serverCron 函数就会调用 rewriteAppendOnlyFileBackground 函数来执行 AOF 重写。serverCron 函数里面的这部分执行逻辑如下所示:
//如果没有RDB子进程,也没有AOF重写子进程,并且AOF重写被设置为待调度执行,那么调用rewriteAppendOnlyFileBackground函数进行AOF重写
if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
server.aof_rewrite_scheduled)
{
rewriteAppendOnlyFileBackground();
}
- 其次,即使 AOF 重写操作没有被设置为待调度执行,serverCron 函数也会周期性判断是否需要执行 AOF 重写。这里的判断条件主要有三个,分别是 AOF 功能已启用、AOF 文件大小比例超出阈值,以及 AOF 文件大小绝对值超出阈值。
当这三个条件都满足时,并且也没有 RDB 子进程和 AOF 子进程在运行的话,此时,serverCron 函数就会调用 rewriteAppendOnlyFileBackground 函数执行 AOF 重写。这部分的代码逻辑如下所示:
//如果AOF功能启用、没有RDB子进程和AOF重写子进程在执行、AOF文件大小比例设定了阈值,以及AOF文件大小绝对值超出了阈值,那么,进一步判断AOF文件大小比例是否超出阈值
if (server.aof_state == AOF_ON && server.rdb_child_pid == -1 && server.aof_child_pid == -1 && server.aof_rewrite_perc && server.aof_current_size > server.aof_rewrite_min_size) {
//计算AOF文件当前大小超出基础大小的比例
long long base = server.aof_rewrite_base_size ? server.aof_rewrite_base_size : 1;
long long growth = (server.aof_current_size*100/base) - 100;
//如果AOF文件当前大小超出基础大小的比例已经超出预设阈值,那么执行AOF重写
if (growth >= server.aof_rewrite_perc) {
...
rewriteAppendOnlyFileBackground();
}
}
AOF 重写的基本过程
rewriteAppendOnlyFileBackground 函数,主体逻辑比较简单,一方面,它会通过调用 fork 函数创建一个子进程,然后在子进程中调用 rewriteAppendOnlyFile 函数进行 AOF 文件重写。
rewriteAppendOnlyFile 函数是在 aof.c 文件中实现的。它主要会调用 rewriteAppendOnlyFileRio 函数(在 aof.c 文件中)来完成 AOF 日志文件的重写。具体来说,就是 rewriteAppendOnlyFileRio 函数会遍历 Redis server 的每一个数据库,把其中的每个键值对读取出来,然后记录该键值对类型对应的插入命令,以及键值对本身的内容。
另一方面,在父进程中,这个 rewriteAppendOnlyFileBackground 函数会把 aof_rewrite_scheduled 变量设置为 0,同时记录 AOF 重写开始的时间,以及记录 AOF 子进程的进程号。
rewriteAppendOnlyFileBackground 函数还会调用 updateDictResizePolicy 函数,禁止在 AOF 重写期间进行 rehash 操作。这是因为 rehash 操作会带来较多的数据移动操作,对于 AOF 重写子进程来说,这就意味着父进程中的内存修改会比较多。因此,AOF 重写子进程就需要执行更多的写时复制,进而完成 AOF 文件的写入,这就会给 Redis 系统的性能造成负面影响。
int rewriteAppendOnlyFileBackground(void) {
...
if ((childpid = fork()) == 0) { //创建子进程
...
//子进程调用rewriteAppendOnlyFile进行AOF重写
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
size_t private_dirty = zmalloc_get_private_dirty(-1);
...
exitFromChild(0);
} else {
exitFromChild(1);
}
}
else{ //父进程执行的逻辑
...
server.aof_rewrite_scheduled = 0;
server.aof_rewrite_time_start = time(NULL);
server.aof_child_pid = childpid; //记录重写子进程的进程号
updateDictResizePolicy(); //关闭rehash功能
}
此文章为10月Day19学习笔记,内容来源于极客时间《Redis 源码剖析与实战》