需要通过aof记录的命令主要为set命令和过期命令,这些命令在执行完毕后需要记录到aof缓冲区中以便写入到aof文件中。
sds aof_buf; /* AOF buffer, written before entering the event loop */
在结构体redisServer中通过一个sds来作为aof的缓冲区。
Redis服务器在执行完相应的命令的时候,如果开启了aof功能,就会在feedAppendOnlyFile()函数中准备将命令添加至aof缓冲区当中。
void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int argc) {
sds buf = sdsempty();
robj *tmpargv[3];
/* The DB this command was targeting is not the same as the last command
* we appended. To issue a SELECT command is needed. */
if (dictid != server.aof_selected_db) {
char seldb[64];
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 ||
cmd->proc == expireatCommand) {
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
} else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
/* Translate SETEX/PSETEX to SET and PEXPIREAT */
tmpargv[0] = createStringObject("SET",3);
tmpargv[1] = argv[1];
tmpargv[2] = argv[3];
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
decrRefCount(tmpargv[0]);
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
} else if (cmd->proc == setCommand && argc > 3) {
int i;
robj *exarg = NULL, *pxarg = NULL;
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */
buf = catAppendOnlyGenericCommand(buf,3,argv);
for (i = 3; i < argc; i ++) {
if (!strcasecmp(argv[i]->ptr, "ex")) exarg = argv[i+1];
if (!strcasecmp(argv[i]->ptr, "px")) pxarg = argv[i+1];
}
serverAssert(!(exarg && pxarg));
if (exarg)
buf = catAppendOnlyExpireAtCommand(buf,server.expireCommand,argv[1],
exarg);
if (pxarg)
buf = catAppendOnlyExpireAtCommand(buf,server.pexpireCommand,argv[1],
pxarg);
} else {
/* All the other commands don't need translation or need the
* same translation already operated in the command vector
* for the replication itself. */
buf = catAppendOnlyGenericCommand(buf,argc,argv);
}
/* Append to the AOF buffer. This will be flushed on disk just before
* of re-entering the event loop, so before the client will get a
* positive reply about the operation performed. */
if (server.aof_state == AOF_ON)
server.aof_buf = sdscatlen(server.aof_buf,buf,sdslen(buf));
/* If a background append only file rewriting is in progress we want to
* accumulate the differences between the child DB and the current one
* in a buffer, so that when the child process will do its work we
* can append the differences to the new append only file. */
if (server.aof_child_pid != -1)
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
sdsfree(buf);
}
首先,判断该命令的执行dbid是否是和当前的dbid一致,如果不一致,需要额外写入select命令切换到命令所执行的dbid上。
而后,如果该命令属于expire,perpire, expireat这三个纯粹设置过期时间的命令,直接通过catAppendOnlyExpireAtCommand()方法转换成过期命令写到结果sds中。
对于setex和psetex两个存放和过期键同时设置的命令,首先根据这两条命令的格式:
SETEX key seconds value,取到第一个和第三个参数,通过catAppendOnlyGenericCommand()方法转换成set命令写到aof缓冲区中,再根据第一个参数和第二个参数组成pexpireat命令写到结果sds中,在这里被转换成了两条命令。
而如果是set [ex seconds] [px millisecond] 形式的命令,则根据参数数数量来确定。
如果是这样的命令,也是拆分成set部分和pexpireat部分拆分为多段命令。
如果只是简单的set命令,就简单通过catAppendOnlyGenericCommand()方法插入结果sds。
在完成了上述操作后,判断如果开启了aof,那么则将结果sds写入到server的aof缓冲区中。
如果此时redis正有子进程进行重写操作,那么直接将新的sds加入到重写的内容中。
最后清空结果sds以节约空间。
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);
for (j = 0; j < argc; j++) {
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;
}
catAppendOnlyGenericCommand()方法用来将set命令转换为sds以便写入到aof缓冲区中,每次在写命令的开头,都会计算包括命令和参数的个数总和,加上*号写在命令上一行。同时每次在写具体值之前,都要计算写入的参数的具体长度,使用$加在前一行。作为解析对象的o每次在使用玩一次,都会通过decrRefCount()方法减少一次引用次数便于回收。