AOF 日志
本文详细介绍了 Redis 6 的 AOF 后台生成策略,包括 Linux I/O 缓冲、AOF 持久化过程以及 AOF 重写过程、AOF 文件加载过程等。
如无特殊说明,本文涉及的源码均位于 aof.c 和 aof.h 中
AOF 简介
AOF 是 “Append Only File” 的缩写,它存储的是 Redis 服务器的顺序指令序列,并且只记录对内存进行修改的指令。假设 AOF 日志记录了自 Redis 实例创建以来所有的修改性指令序列,那么便可以通过对一个空的 Redis 实例顺序执行所有指令来恢复 Redis 当前实例的内存数据结构的状态,即“重放”。
Redis 包含两种与 AOF 相关的操作:
- AOF 持久化:客户端向 Redis 服务器发送增、删、改指令时,这些指令会被传播到 AOF 缓冲区,并随后按照特定策略持久化至磁盘。
- AOF 重写:由于 AOF 文件保存的是通过 RESP 协议封装的文本内容,会占用大量的磁盘空间,因此 Redis 会定期重写 AOF 日志,以达到文件瘦身的效果。
AOF 机制的常用配置如下:
appendonly
:是否启用 AOF 机制,默认为no
。appendfilename
:AOF 文件名,默认值为appendonly.aof
。appendfsync
:日志刷盘策略:everysec(默认值)
:表示每秒最多调用一次fsync
。每次将数据写入文件缓冲区后,将刷盘任务提交给 BIO 后台线程异步执行,提交任务的频率由 BIO 控制。always
:每次将数据写入文件缓冲区后(write
调用),立即执行fsync
操作以实现同步。no
:只写入文件缓冲区,不手动刷盘,具体的落盘时机交给操作系统控制。
no-appendfsync-on-rewrite
:默认值为no
,表示当有后台子进程生成 RDB 或者重写 AOF 时,仍允许 AOF 缓冲区数据同步到磁盘。auto-aof-rewrite-percentage
:指定百分比,当 AOF 文件大小超过原来的x%
时进行重写,默认值为 100%。指定百分比为 0 时会禁用 AOF 自动重写。auto-aof-rewrite-min-size
:当 AOF 文件的大小超过设定值(默认 64MB)时进行重写。aof-use-rdb-preamble
:是否开启 AOF 混合持久化,默认值为yes
。
类 Unix 系统的 I/O 与缓存
在正式介绍 AOF 生成机制之前,我们先来了解一些前置技术。
内核缓冲区
与内存操作相比,磁盘 I/O 的代价非常昂贵,因此类 Unix 系统的内核通常会定义内核缓冲区,只有当缓冲区已满时才将数据写入磁盘。
以 POSIX 标准定义的 I/O 函数 read
、write
为例,它们本身并不带有任何缓冲,而是直接使用内核提供的缓冲。当我们调用 write
函数写入文件时,数据会先被存入内核缓冲区。当缓冲区已满,或者手动调用 fsync
函数时,再将内核缓冲区中的数据同步到磁盘上。
fdatasync
POSIX 标准定义的 fsync
函数在文件元数据(metadata,例如 st_size
、st_atime
、st_mtime
等)变脏时,会将所有元数据同步到磁盘。由于每次同步都必定导致时间戳的改变,而且文件内容和文件元数据通常存储在磁盘上的不同位置,因此每次调用 fsync
至少需要两次随机磁盘 I/O,其代价十分昂贵。
为此,Linux 平台提供了一个 fdatasync
函数。该函数仅在必要时才将元数据同步到磁盘(文件读写时间戳、文件大小等信息的改变不会实时落盘),大大降低了元数据同步的频率。
Redis 通过条件编译,将 Linux 平台的 redis_fsync
定义成了 fdatasync
,而在其它类 Unix 平台上依旧是 fsync
:
#ifdef __linux__
#define redis_fsync fdatasync
#else
#define redis_fsync fsync
#endif
I/O 缓冲区
上面介绍的 POSIX 标准的 I/O 函数虽然可以利用内核缓冲来加速读写,但由于缓存数据时需要切换到内核态,因此开销比较大。
实际上,缓存操作最好在用户态进行。因此,C 语言标准库提供了位于用户态的 I/O 缓冲区,并定义了一系列基于该缓冲区的 I/O 函数,例如 fopen
、fwrite
、fread
、fflush
等。与 POSIX 标准函数相比,这些函数更适合处理大文件的读写。因此,在 Redis 中,AOF 重写过程是通过 C 标准库函数完成的。而对于数据量较小的 AOF 文件追加操作,则直接调用了 POSIX 标准函数。
C 标准库提供的 I/O 函数本质上是对 POSIX 标准函数的封装,因此当我们通过 fwrite
将数据写入 I/O 缓冲后:
- 首先需要调用
fflush
将数据写入内核缓冲。由于fflush
通常比内核缓冲区大,因此这个调用可能会触发多次落盘操作(顺序写,性能并不差)。 fflush
完毕后,内核缓冲区中往往还会有剩余数据。因此,还需要调用 POSIX 标准的fsync
兜底。
AOF 持久化过程
AOF 持久化过程分为以下三个步骤:命令传播、刷新 AOF 缓冲区和同步磁盘。示意图如下:
![AOF 持久化过程](images/redis-aof-write.webp)
命令传播至 AOF 缓冲
配置中启用 AOF 后,Redis 会将执行的每个写命令都传播到 AOF 缓冲区 server.aof_buf
。该过程由 propagate()
函数触发(propagate 由 call 函数触发):
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc,
int flags) {
...
if (server.aof_state != AOF_OFF && flags & PROPAGATE_AOF)
feedAppendOnlyFile(cmd,dbid,argv,argc);
...
}
处理 AOF 的实际函数为 feedAppendOnlyFile()
:
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty();
...
// [1] EXPIRE/PEXPIRE/EXPIREAT 转换为 PEXPIREAT 处理
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
cmd->proc == expireatCommand) {
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
} else if (cmd->proc == setCommand && argc > 3) {
// [2] 带 EX、PX 参数的 SET 命令,特殊处理
...
} else {
// [3] 按照 RESP 协议组装命令
buf = catAppendOnlyGenericCommand(buf,argc,argv);
}
// [4] 将组装好的命令追加到 aof_buf
if (server.aof_state == AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
// [5] 如果存在子进程在重写 AOF,则将命令追加到重写缓冲区
if (server.child_type == CHILD_TYPE_AOF)
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
sdsfree(buf);
}
[1]
:对于EXPIRE
、EXPIREAT
和PEXPIRE
将其转换为PEXPIREAT
特殊处理;[2]
:对于带EX
、PX
参数的SET
命令特殊处理,主要涉及过期时间的处理;[3]
:对于其它命令,调用catAppendOnlyGenericCommand
按照 RESP 协议组装命令,并将其暂存至 buf;[4]
:如果启用 AOF 日志,则将 buf 中暂存的命令追加到 AOF 缓冲区server.aof_buf
;[5]
:如果存在正在重写 AOF 的子进程,则将命令追加到 AOF 重写缓冲区server.aof_rewrite_buf_blocks
。在允许发送增量数据的情况下,触发可写事件,将增量数据写入 pipe 管道。
刷新 AOF 缓冲区 & 同步磁盘
AOF 数据的落盘操作由 serverCron()
定时任务触发:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
// [1] 存在延迟的 AOF 落盘操作,则在这里完成
if (server.aof_state == AOF_ON && server.aof_flush_postponed_start)
flushAppendOnlyFile(0);
run_with_period(1000) {
// [2] 上次 AOF 落盘失败,这里需要再次落盘
if (server.aof_state == AOF_ON && server.aof_last_write_status == C_ERR)
flushAppendOnlyFile(0);
}
...
}
[1]
:如果server.aof_flush_postponed_start
不为 0,表示存在延迟的 AOF 缓冲区落盘操作,在这里落盘;[2]
:如果server.aof_last_write_status == C_ERR
,表示上次 AOF 落盘失败,这里需要再次落盘。这里会通过
run_with_period
宏控制执行周期,宏的实现如下:以默认情况为例,#define run_with_period(_ms_) if ((_ms_ <= 1000/server.hz) || !(server.cronloops%((_ms_)/(1000/server.hz))))
1000/server.hz
等于 100,_ms_
参数值为 1000,那么最终(_ms_)/(1000/server.hz)
的值等于 10。也就是随着server.cronloops
的自增,每执行十次serverCron()
,该宏返回一次 true。
核心实现为 flushAppendOnlyFile()
:
void flushAppendOnlyFile(int force) {
ssize_t nwritten;
int sync_in_progress = 0;
mstime_t latency;
// [1] 如果 AOF 缓冲为空,则特殊处理
if (sdslen(server.aof_buf) == 0) {
if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
server.aof_fsync_offset != server.aof_current_size &&
server.unixtime > server.aof_last_fsync &&
!(sync_in_progress = aofFsyncInProgress())) {
goto try_fsync;
} else {
return;
}
}
// [2] 判断是否有后台的 bio 线程
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
sync_in_progress = aofFsyncInProgress();
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
if (sync_in_progress) {
if (server.aof_flush_postponed_start == 0) {
server.aof_flush_postponed_start = server.unixtime;
return;
} else if (server.unixtime - server.aof_flush_postponed_start < 2) {
return;
}
server.aof_delayed_fsync++;
}
}
...
// [3] 刷新 aof_buf 缓冲区的数据到 AOF 文件
nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
...
server.aof_flush_postponed_start = 0;
...
// [4] 处理用完的 AOF 缓冲
if ((sdslen(server.aof_buf)+sdsavail(server.aof_buf)) < 4000) {
sdsclear(server.aof_buf);
} else {
sdsfree(server.aof_buf);
server.aof_buf = sdsempty();
}
[1]
:如果 AOF 缓冲为空,并且 AOF 策略配置为每秒同步 (everysec),距离上次磁盘同步已经过去 1 秒,同时当前没有正在运行的 bio 后台任务,则跳转至[7]
执行后台磁盘同步。否则,直接退出函数;[2]
:如果 AOF 配置的策略为每秒同步 (everysec),且后台存在正在同步磁盘的 bio 线程,则判断aof_flush_postponed_start
是否为 0:- 如果这个值为 0,表示之前没有推迟过落盘任务,所以记录当前的时间戳到
aof_flush_postponed_start
并退出; - 如果这个值不为 0,表示之前已经推迟过了。所以判断当前距离
aof_flush_postponed_start
是否已经过去了 2s。如果是,则增加server.aof_delayed_fsync
计数,强制后续的磁盘同步流程;
- 如果这个值为 0,表示之前没有推迟过落盘任务,所以记录当前的时间戳到
[3]
:调用aofWrite()
(即 POSIX 标志的write()
函数)将aof_buf
中的数据写入 AOF 文件的内核缓冲;[4]
:如果aof_buf
的总空间小于 4KB,则清空 buffer 内容并重用该缓冲,否则创建一个新的aof_buf
。
处理完成 aof_buf
中的数据后,接下来就该处理磁盘同步过程了:
try_fsync:
// [5] 判断是否有运行中的 bio 线程
if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
return;
// [6] 如果 AOF 落盘策略为 always,直接同步
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
...
if (redis_fsync(server.aof_fd) == -1)
...
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
server.unixtime > server.aof_last_fsync)) {
// [7] 后台同步 aof
if (!sync_in_progress) {
aof_background_fsync(server.aof_fd);
server.aof_fsync_offset = server.aof_current_size;
}
server.aof_last_fsync = server.unixtime;
}
}
[5]
:如果用户配置了no-appendfsync-on-rewrite = yes
,且当前后台存在处理刷盘的 bio 线程,则直接返回;[6]
:如果 AOF 落盘策略为 always,直接调用 redis_fsync() 完成落盘;[7]
:如果不存在后台 bio 线程,且用户配置的 AOF 策略为每秒同步 (everysec),则调用aof_background_fsync()
创建 bio 异步任务。
AOF 重写过程
当 AOF 文件过大时,恢复过程可能会变得异常缓慢。因此,Redis 提供了 AOF 重写机制,以对 AOF 日志进行压缩。从 4.0 版本开始,Redis 引入了 AOF 混合持久化功能,该功能在 AOF 重写时会先将字典数据以 RDB 形式写入文件,然后再追加重写期间生成的新字符串。
AOF 重写过程分为以下几个步骤:
- 主进程 fork 一个 AOF 重写进程;
- AOF 重写进程将内存中的快照数据保存到 AOF 临时文件中,这个过程会根据
aof-use-rdb-preamble
的配置决定序列化格式; - 重写期间,主进程继续将新到达的写命令追加到原 AOF 文件,同时将这些指令拷贝到重写缓冲,然后通过 pipe 管道发送给 AOF 进程的差异缓冲。
- 子进程处理完快照数据后,追加差异缓冲中的数据到临时 AOF 文件,然后禁止主进程继续发送新数据;
- 主进程再将重写缓冲中的剩余数据追加到临时 AOF 文件,然后用临时 AOF 文件原子替换旧的 AOF 文件,完成收尾工作。
示意图如下:
![AOF 重写过程](images/redis-aof-rewrite.webp)
接下来,让我们详细介绍这个过程。
定时任务触发 fork 子进程
AOF 的重写过程由 serverCron()
定时任务触发:
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
// [1] 存在延迟的 AOF 重写操作,则在这里完成
if (!hasActiveChildProcess() &&
server.aof_rewrite_scheduled) {
rewriteAppendOnlyFileBackground();
}
if (hasActiveChildProcess() || ldbPendingChildren()) {
run_with_period(1000) receiveChildInfo();
// 主进程收尾
checkChildrenDone();
} else {
...
// [2] 条件满足时,后台重写 AOF
if (server.aof_state == AOF_ON &&
!hasActiveChildProcess() &&
server.aof_rewrite_perc &&
server.aof_current_size > server.aof_rewrite_min_size)
{
long long base = server.aof_rewrite_base_size ?
server.aof_rewrite_base_size : 1;
long long growth = (server.aof_current_size*100/base) - 100;
if (growth >= server.aof_rewrite_perc) {
...
rewriteAppendOnlyFileBackground();
}
}
}...
}
[1]
:如果server.aof_rewrite_scheduled
不为 0,表示存在延迟的 AOF 重写操作,如果当前不存在运行的子进程,则执行 AOF 后台重写;[2]
:如果服务器启用了 AOF 功能,并且满足以下条件,将执行 AOF 后台重写:- 不存在运行中的子进程;
- 当前 AOF 文件大小大于
server.aof_rewrite_min_size
配置; - 当前 AOF 文件大小,对比上次重写后 AOF 文件大小的比例,超过了
auto-aof-rewrite-percentage
配置的百分比;
核心实现为 rewriteAppendOnlyFileBackground()
:
int rewriteAppendOnlyFileBackground(void) {
...
// [1] 创建父子进程通信用的 pipe 管道
if (aofCreatePipes() != C_OK) return C_ERR;
// [2] 创建 AOF 子进程
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
char tmpfile[256];
// [3] 子进程执行这里
redisSetProcTitle("redis-aof-rewrite");
// 将自己绑定到某个 cpu
redisSetCpuAffinity(server.aof_rewrite_cpulist);
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
// 重写子进程
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");
exitFromChild(0);
} else {
exitFromChild(1);
}
} else {
// 主进程执行这里
...
return C_OK;
}
return C_OK;
}
[1]
:调用aofCreatePipes()
创建父子进程之间的管道:server.aof_pipe_write_data_to_child //主进程向子进程发送数据 server.aof_pipe_read_data_from_parent //子进程读取主进程发来的数据 server.aof_pipe_write_ack_to_parent //子进程向主进程发送停止信号 server.aof_pipe_read_ack_from_child //主进程读取子进程发来的停止信号 server.aof_pipe_write_ack_to_child //主进程收到停止信号后,向子进程回复确认 server.aof_pipe_read_ack_from_parent //子进程读取主进程发来的回复确认
这 6 个管道端口两两一对,以单工模式工作,组成了一条数据管道和两条控制管道。
[2]
:创建 AOF 子进程。[3]
:子进程调用rewriteAppendOnlyFile()
完成 AOF 日志的重写操作。
子进程处理过程
子进程处理 AOF 文件的核心实现是 rewriteAppendOnlyFile()
函数:
int rewriteAppendOnlyFile(char *filename) {
...
// [1] 打开一个 aof 临时文件
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
fp = fopen(tmpfile,"w");
...
// [2] 初始化差异缓冲
server.aof_child_diff = sdsempty();
// 初始化 rio 抽象层
rioInitWithFile(&aof,fp);
// 设定 fsync 触发条件
if (server.aof_rewrite_incremental_fsync)
rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
startSaving(RDBFLAGS_AOF_PREAMBLE);
if (server.aof_use_rdb_preamble) {
// [3] 混合持久化
if (rdbSaveRio(&aof,&error,RDBFLAGS_AOF_PREAMBLE,NULL) == C_ERR) {
...
}
} else {
// [4] 普通持久化
if (rewriteAppendOnlyFileRio(&aof) == C_ERR) goto werr;
}
// [5] 将 I/O 缓冲和内核缓冲中的剩余数据落盘
if (fflush(fp) == EOF) goto werr;
if (fsync(fileno(fp)) == -1) goto werr;
[1]
:打开 aof 临时文件,命名规则为temp-rewriteaof-%d.aof
;[2]
:初始化接收增量命令的差异缓冲server.aof_child_diff
;[3]
:如果启用了混合持久化,则调用 rdbsave 将 RDB 数据写入 aof 临时文件;[4]
:否则调用rewriteAppendOnlyFileRio()
进行普通的 aof 重写。其内部会遍历字典快照,删除无效数据后,将其封装为 RESP 数据写入临时文件。在遍历的过程中,还会周期性地从管道中拉取增量数据到
aof_child_diff
。虽然差异缓冲中的数据在字典快照处理完后才会使用,但是为了降低管道传输压力,Redis 选择在遍历期间就分批将数据拉过来。
[5]
:将 I/O 缓冲和内核缓冲中的剩余数据同步到磁盘;// [6] 从管道中拉取剩余的增量数据,持续一段时间 int nodata = 0; mstime_t start = mstime(); while(mstime()-start < 1000 && nodata < 20) { if (aeWait(server.aof_pipe_read_data_from_parent, AE_READABLE, 1) <= 0) { nodata++; continue; } nodata = 0; aofReadDiffFromParent(); } // [7] 通知主进程停止发送增量数据 if (write(server.aof_pipe_write_ack_to_parent,"!",1) != 1) goto werr; if (anetNonBlock(NULL,server.aof_pipe_read_ack_from_parent) != ANET_OK) goto werr; // 等待主进程的 ACK,最多等 5s if (syncRead(server.aof_pipe_read_ack_from_parent,&byte,1,5000) != 1 || byte != '!') goto werr; ... // [8] 收到 ACK 后,再从管道拉一次数据,防止服务器收到停止信号前,又发出数据 aofReadDiffFromParent(); ... // [9] 将差异缓冲数据写入 aof 文件 while (bytes_to_write) { ... if (rioWrite(&aof,buf,chunk_size) == 0) goto werr; ... } // [10] 将 aof 文件缓冲中的数据,同步到磁盘 if (fflush(fp)) goto werr; if (fsync(fileno(fp))) goto werr; if (fclose(fp)) { fp = NULL; goto werr; } fp = NULL; // [11] 重命名文件,确保写入成功 if (rename(tmpfile,filename) == -1) { ... return C_ERR; } ... }
[6]
:最多持续 1s,继续从管道中拉取增量数据。如果超时,或连续 20ms 没有收到增量数据,就停止拉取;[7]
:停止拉取后,通过控制管靠发送指令,让主进程停止向管道写入数据,然后等待主进程地 ACK;[8]
:主进程收到指令之前,仍可能继续向管道写入数据,所以这里需要再拉取一次,确保没有数据丢失;[9]
:将差异缓冲中的数据追加到 aof 文件;[10]
:再次将 aof 文件缓冲中的数据同步到磁盘;[11]
:修改临时文件名,确认文件写入成功。这里的重命名是将
temp-rewriteaof-%d.aof
修改为temp-rewriteaof-bg-%d.aof
,修改后的文件依旧是临时文件,重命名只是为了确认文件内容、元数据等信息落盘成功。
主进程收尾工作
当主进程收到子进程停止发送增量数据的请求后,会删除数据管道上的回调函数,从而停止发送增量数据。然后在下一次运行 serverCron()
定时任务时,调用 checkChildrenDone()
完成 AOF 收尾工作。其核心方法为 backgroundRewriteDoneHandler()
:
void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
if (!bysignal && exitcode == 0) {
...
// [1] 打开子进程刚刚处理完的 aof 临时文件
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
(int)server.child_pid);
newfd = open(tmpfile,O_WRONLY|O_APPEND);
...
// [2] 将停止发送增量数据期间积累的数据追加到文件
if (aofRewriteBufferWrite(newfd) == -1) {
...
}
...
// [3] 替换旧的 aof 文件
if (rename(tmpfile,server.aof_filename) == -1) {
...
}
...
}
cleanup:
// [4] 删除管道,清空 aof_buf,和所有临时文件
aofClosePipes();
aofRewriteBufferReset();
aofRemoveTempFile(server.child_pid);
...
}
[1]
:打开子进程刚刚处理完的 aof 临时文件;[2]
:将停止发送增量数据期间,重写缓冲中积累的数据追加到临时文件;[3]
:用临时文件替换旧的 aof 文件;[4]
:删除管道,清空aof_buf
,和所有临时文件;
可见,为了重写过程的安全性(一旦重写失败,原 AOF 文件依然可用),重写期间的增量命令会被写入磁盘两次(主进程继续写到旧 AOF,子进程最终追加到新 AOF),会浪费一定的磁盘资源。
此外,在重写过程中,新到达的写命令会被全部存储到子进程的差异缓冲区中,这可能导致较高的内存占用。
AOF 文件加载过程
Redis 在 启动过程 中调用 loadDataFromDisk()
来完成 AOF 的加载,调用链路如下:loadDataFromDisk -> loadAppendOnlyFile
。最终在 loadAppendOnlyFile()
函数中完成对 AOF 文件的加载:
int loadAppendOnlyFile(char *filename) {
struct client *fakeClient;
// [1] 打开 AOF 文件
FILE *fp = fopen(filename,"r");
...
// [2] 创建虚拟客户端
fakeClient = createAOFClient();
startLoadingFile(fp, filename, RDBFLAGS_AOF_PREAMBLE);
// [3] 根据是否有 RDB 前导码,确定处理方式
char sig[5]; /* "REDIS" */
if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) {
/* No RDB preamble, seek back at 0 offset. */
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
} else {
/* RDB preamble. Pass loading the RDB functions. */
rio rdb;
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
rioInitWithFile(&rdb,fp);
// 加载 RDB 内容
if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) ...
}
// [4] 循环处理剩下的所有指令
while(1) {
...
// [5] 按照 RESP 协议读取命令的参数数量
argc = atoi(buf+1);
if (argc < 1) goto fmterr;
argv = zmalloc(sizeof(robj*)*argc);
fakeClient->argc = argc;
fakeClient->argv = argv;
// [6] 开始读取每一个参数
for (j = 0; j < argc; j++) {
...
}
// [7] 根据第一个参数,查询命令表
cmd = lookupCommand(argv[0]->ptr);
...
// [8] 执行命令
fakeClient->cmd = fakeClient->lastcmd = cmd;
if (fakeClient->flags & CLIENT_MULTI &&
fakeClient->cmd->proc != execCommand) {
queueMultiCommand(fakeClient);
} else {
cmd->proc(fakeClient);
}
...
}
...
}
[1]
:打开 AOF 文件;[2]
:创建一个虚拟客户端,用于执行 AOF 文件中的命令;[3]
:如果 AOF 文件以REDIS
字符串开头,则调用 rdbLoadRio 加载 RDB 类型的数据。否则将文件指针归零;[4]
:正式开始处理 RESP 格式的字符串:[5]
:按照 RESP 协议读取命令的参数数量;[6]
:读取每一个参数;[7]
:根据第一个参数,去查询命令表中的指令类型;[8]
:调用redisCommand.proc
函数执行命令。
至此,Redis AOF 生成机制的主要内容就介绍完了。