来聊聊redis的AOF写入
# 写在文章开头
最近比较忙碌,所以关于AOF的解读笔者会分为3篇进行解读,而本文会解读的是AOF写入的核心流程。

Hi,我是 sharkChili ,是个不断在硬核技术上作死的 java coder ,是 CSDN的博客专家 ,也是开源项目 Java Guide 的维护者之一,熟悉 Java 也会一点 Go ,偶尔也会在 C源码 边缘徘徊。写过很多有意思的技术博客,也还在研究并输出技术的路上,希望我的文章对你有帮助,非常欢迎你关注我的公众号: 写代码的SharkChili 。
因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。
# 详解AOF的写入
# 简述AOF写入主要流程
本质上redis在启动时会通过服务端对象redisServer进行维护,每当用户通过客户端传入执行指令时,redis会解析改指令并将该指令转为AOF格式的字符串写入到AOF缓冲区aof_buf:

# AOF缓冲区
对应的将文件写入文件前的解析到的字符串,redis会将其存到redisServer的aof缓冲区aof_buf中,我们从redisServer 的定义中就可以看到这个缓冲区的定义即一个sds字符串:
struct redisServer {
//......
//aof缓冲区
sds aof_buf;
//......
}
2
3
4
5
6
7
# 写入指令至AOF缓冲区
每当redis解析出客户端的指令之后,就会调用call方法,可以看到其内部会调用c->cmd->proc(c)调用proc指针所指向的redis指令,以笔者上图为例proc对应的指令就是setCommand,然后调用propagate将指令信息传播到aof链路进行aof数据写入:
void call(redisClient *c, int flags) {
//......
//传入解析后的指令和键值对完成数据库操作
c->cmd->proc(c);
//......
/* Propagate the command into the AOF and replication link */
//将指令传播到aof链路
if (flags & REDIS_CALL_PROPAGATE) {
//......
//如果允许传播则调用propagate传入指令和数据库信息、键值对进行AOF数据写入
if (flags != REDIS_PROPAGATE_NONE)
propagate(c->cmd,c->db->id,c->argv,c->argc,flags);
}
//......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
最终feedAppendOnlyFile就会将指令和键值对以及数据信息解析生字符串然后写入到aof缓冲区中,这一段流程我们可以在feedAppendOnlyFile这个函数中看到写入数据id和set键值对字符串并写入aof缓冲区的过程:
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty();
robj *tmpargv[3];
//......
if (dictid != server.aof_selected_db) {
char seldb[64];
//解析数据库id写入缓冲区中
snprintf(seldb,sizeof(seldb),"%d",dictid);
buf = sdscatprintf(buf,"*2\r\n$6\r\nSELECT\r\n$%lu\r\n%s\r\n",
(unsigned long)strlen(seldb),seldb);
server.aof_selected_db = dictid;
}
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
//......
} else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
//......
} else {
//解析键值对key value生成字符串写入buf中
buf = catAppendOnlyGenericCommand(buf,argc,argv);
}
//......
//如果开启aof则将buf写入aof_buf缓冲区
if (server.aof_state == REDIS_AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
//......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
这里我们一并查看catAppendOnlyGenericCommand,其内部逻辑最核心的逻辑就是将指令字符串添加到dst也就是上文的buf字符串中,以笔者为例这段指令set k v最终就会解析成
# 写入数据库0的信息,后续aof恢复时就可以通过select 0定位到数据库中
*2\r\n$6\r\nSELECT\r\n$1\r\n0\r\n
# 字符串长度为3的set指令
*3\r\n$3\r\nset\r\n
# 1个字符串长度的key为k
$1\r\nk\r\n
# 1个字符串长度value为v
$1\r\nv\r\n
2
3
4
5
6
7
8
9
对应的我们给出catAppendOnlyGenericCommand印证笔者的逻辑:
sds catAppendOnlyGenericCommand(sds dst, int argc, robj **argv) {
char buf[32];
int len, j;
robj *o;
buf[0] = '*';
len = 1+ll2string(buf+1,sizeof(buf)-1,argc);
buf[len++] = '\r';
buf[len++] = '\n';
dst = sdscatlen(dst,buf,len);
//基于参数个数解析字符串,以笔者传入的指令就是set k v
for (j = 0; j < argc; j++) {
//按照指定格式将set k v存入buf中
o = getDecodedObject(argv[j]);
buf[0] = '$';
len = 1+ll2string(buf+1,sizeof(buf)-1,sdslen(o->ptr));
buf[len++] = '\r';
buf[len++] = '\n';
dst = sdscatlen(dst,buf,len);
dst = sdscatlen(dst,o->ptr,sdslen(o->ptr));
dst = sdscatlen(dst,"\r\n",2);
decrRefCount(o);
}
return dst;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# AOF数据刷盘
完成写入之后,redis的事件轮询的循环代码段会执行beforeSleep方法,其内会调用flushAppendOnlyFile将缓冲区数据写入aof文件中appendonly.aof中:

这里我们给出beforeSleep的核心代码,可以看到其内部调用了flushAppendOnlyFile方法:
void beforeSleep(struct aeEventLoop *eventLoop) {
//......
//执行缓冲区数据刷盘
flushAppendOnlyFile(0);
}
2
3
4
5
步入flushAppendOnlyFile即可看到数据数据刷盘的操作函数write:
#define AOF_WRITE_LOG_ERROR_RATE 30 /* Seconds between errors logging. */
void flushAppendOnlyFile(int force) {
//......
//将aof文件刷到物理磁盘中
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
//......
}
2
3
4
5
6
7
此时我们就可以通过cat appendonly.aof看到对应的aof文件:
*2
$6
SELECT
$1
0
*3
$3
set
$1
k
$1
v
2
3
4
5
6
7
8
9
10
11
12
13
14
# 如果我现在用的是这个 java的api向redis写入数据 ,如果返回的是成功 ok,而且redis.conf中对于aof是always模式,是不是就证明一定刷盘成功了?
很明显不是的,从上述的源码中可以看出aof对应的操作大体是先将持久化数据写入aof的buf缓冲区,然后执行redis服务端内部定时任务将数据写入本地,这意味着在写入缓冲区后、持久化前,如果redis因为某些原因宕机,那么刚刚写入的数据就会丢失:

# 小结
本文从源码解析的角度带读者粗略的了解了一下AOF的写入到落盘的整体流程,希望对你有帮助。
我是 sharkchili ,CSDN Java 领域博客专家,开源项目—JavaGuide contributor,我想写一些有意思的东西,希望对你有帮助,如果你想实时收到我写的硬核的文章也欢迎你关注我的公众号: 写代码的SharkChili 。 因为近期收到很多读者的私信,所以也专门创建了一个交流群,感兴趣的读者可以通过上方的公众号获取笔者的联系方式完成好友添加,点击备注 “加群” 即可和笔者和笔者的朋友们进行深入交流。
# 参考
《redis设计与实现》