关于源码loadSingleAppendOnlyFile
一.初始化一些变量
int loadSingleAppendOnlyFile(char *filename) {
struct client *fakeClient;
struct redis_stat sb;
int old_aof_state = server.aof_state;
long loops = 0;
off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */ /*加载的最新格式良好的命令的偏移量*/
off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */
off_t last_progress_report_size = 0;
int ret = AOF_OK;
二.打开这个文件并检查
sds aof_filepath = makePath(server.aof_dirname, filename);
FILE *fp = fopen(aof_filepath, "r");
if (fp == NULL) {
int en = errno;
if (redis_stat(aof_filepath, &sb) == 0 || errno != ENOENT) {
serverLog(LL_WARNING,"Fatal error: can't open the append log file %s for reading: %s", filename, strerror(en));
sdsfree(aof_filepath);
return AOF_OPEN_ERR;
} else {
serverLog(LL_WARNING,"The append log file %s doesn't exist: %s", filename, strerror(errno));
sdsfree(aof_filepath);
return AOF_NOT_EXIST;
}
}
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
fclose(fp);
sdsfree(aof_filepath);
return AOF_EMPTY;
}
通过makePath构造AOF文件的全路径
然后看能不能正确打开AOF
可以打开就看stat 检查文件大小 是否为0
0就错误
三.更新设置
server.aof_state = AOF_OFF;
client *old_cur_client = server.current_client;
client *old_exec_client = server.executing_client;
fakeClient = createAOFClient();
server.current_client = server.executing_client = fakeClient;
设置为刚才创建的假客户端(因为你执行命令 就像一个假客户端一样)
四.检查是否为AOF格式
char sig[5]; /* "REDIS" */
if (fread(sig,1,5,fp) != 5 || memcmp(sig,"REDIS",5) != 0) {
/* Not in RDB format, seek back at 0 offset. */
读取文件的前5个字符,如果是"REDIS" 则说明该文件是RDB格式文件
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
不是RDB就使用fseek(fp,0,SEEK_SET)将文件的读写位置重新设置到文件开始的位置。
} else {
/* RDB format. Pass loading the RDB functions. */
rio rdb;
int old_style = !strcmp(filename, server.aof_filename);
if (old_style)
检查正在读取的这个RDB文件是否是旧式还是新式
serverLog(LL_NOTICE, "Reading RDB preamble from AOF file...");
else
serverLog(LL_NOTICE, "Reading RDB base file on AOF loading...");
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
rioInitWithFile(&rdb,fp);
if (rdbLoadRio(&rdb,RDBFLAGS_AOF_PREAMBLE,NULL) != C_OK) {
调用rdbLoadRio函数读取并加载RDB格式的数据
if (old_style)
serverLog(LL_WARNING, "Error reading the RDB preamble of the AOF file %s, AOF loading aborted", filename);
else
serverLog(LL_WARNING, "Error reading the RDB base file %s, AOF loading aborted", filename);
ret = AOF_FAILED;
goto cleanup;
} else {
loadingAbsProgress(ftello(fp));
last_progress_report_size = ftello(fp);
if (old_style) serverLog(LL_NOTICE, "Reading the remaining AOF tail...");
ok就更新数据并继续读取
}
}
1.读取文件的前5个字符,如果是"REDIS" 则说明该文件是RDB格式文件(因为AOF重写后可能为RDB)
不是RDB就使用fseek(fp,0,SEEK_SET)
将文件的读写位置重新设置到文件开始的位置
如果是RDB
初始化一个用来读取文件的rio结构体
检查正在读取的这个RDB文件是否是旧式还是新式(因为新版本的AOF是多文件AOF和原先的单个文件AOF不一样)
调用rdbLoadRio
函数读取并加载RDB格式的数据。并看读取RDB的过程中是否发生了错误
如果一切OK,那么就打印数据载入的进度,更新最后一次报告进度的文件大小,接下来会读取AOF的剩余部分
五.正式读取AOF文件
while(1) {
int argc, j;
unsigned long len;
robj **argv;
char buf[AOF_ANNOTATION_LINE_MAX_LEN];
sds argsds;
struct redisCommand *cmd;
if (!(loops++ % 1024)) {
off_t progress_delta = ftello(fp) - last_progress_report_size;
loadingIncrProgress(progress_delta);
last_progress_report_size += progress_delta;
processEventsWhileBlocked();
processModuleLoadingProgressEvent(1);
}
if (fgets(buf,sizeof(buf),fp) == NULL) {
if (feof(fp)) {
break;
} else {
goto readerr;
}
}
if (buf[0] == '#') continue;
if (buf[0] != '*') goto fmterr;
if (buf[1] == '\0') goto readerr;
argc = atoi(buf+1);
if (argc < 1) goto fmterr;
if ((size_t)argc > SIZE_MAX / sizeof(robj*)) goto fmterr;
* argv.
argv = zmalloc(sizeof(robj*)*argc);
fakeClient->argc = argc;
fakeClient->argv = argv;
fakeClient->argv_len = argc;
for (j = 0; j < argc; j++) {
char *readres = fgets(buf,sizeof(buf),fp);
if (readres == NULL || buf[0] != '$') {
fakeClient->argc = j;
freeClientArgv(fakeClient);
if (readres == NULL)
goto readerr;
else
goto fmterr;
}
len = strtol(buf+1,NULL,10);
argsds = sdsnewlen(SDS_NOINIT,len);
if (len && fread(argsds,len,1,fp) == 0) {
sdsfree(argsds);
fakeClient->argc = j;
freeClientArgv(fakeClient);
goto readerr;
}
argv[j] = createObject(OBJ_STRING,argsds);
if (fread(buf,2,1,fp) == 0) {
fakeClient->argc = j+1;
freeClientArgv(fakeClient);
goto readerr;
}
}
一开始定义了参数个数argc 字符串argv buf字符数组
fakeClient->argc = argc;
fakeClient->argv = argv;
fakeClient->argv_len = argc;
而且也将它们指针与假客户端的指针相互绑定
loops++ % 1024
来处理一些事件
然后取出一行 检查第一个字符
if (buf[0] == '#') continue;
if (buf[0] != '*') goto fmterr;
if (buf[1] == '\0') goto readerr;
这里#跳过是因为 redis暂时不需要#后面的内容也就是时间戳
因为我们现在需要读出 后面有几个字符串 而他的个数格式是*数字
比如*3
表示后面有三个字符串
所以如果字符不是*就是错误
把参数存进argc
然后看参数是过多还是过少 报错
更新假客户端
的命令参数相关的属性
for (j = 0; j < argc; j++) {
char *readres = fgets(buf,sizeof(buf),fp);
if (readres == NULL || buf[0] != '$') {
fakeClient->argc = j;
freeClientArgv(fakeClient);
if (readres == NULL)
goto readerr;
else
goto fmterr;
}
len = strtol(buf+1,NULL,10);
argsds = sdsnewlen(SDS_NOINIT,len);
if (len && fread(argsds,len,1,fp) == 0) {
sdsfree(argsds);
fakeClient->argc = j;
freeClientArgv(fakeClient);
goto readerr;
}
argv[j] = createObject(OBJ_STRING,argsds);
用于处理命令参数之间的分隔符等信息。
if (fread(buf,2,1,fp) == 0) {
fakeClient->argc = j+1;
freeClientArgv(fakeClient);
goto readerr;
}
}
循环argc个数次 读取数据
检查读取到的行是否以 ‘$’ 开头,如果不是,则表示格式错误
$数字表示我要读取 数字长度的字符串
$3 SET
然后读取该长度的字符串
argv[j] = createObject(OBJ_STRING,argsds);
存到argv[当前循环]
最后处理命令的分隔符
六.命令是否符合规范
/* Command lookup */
cmd = lookupCommand(argv,argc);
if (!cmd) {
serverLog(LL_WARNING,
"Unknown command '%s' reading the append only file %s",
(char*)argv[0]->ptr, filename);
freeClientArgv(fakeClient);
ret = AOF_FAILED;
goto cleanup;
}
if (cmd->proc == multiCommand) valid_before_multi = valid_up_to;
通过查找[0] [1]两个字符串在不在字典里就行
struct redisCommand *lookupCommandLogic(dict *commands, robj **argv, int argc, int strict) {
struct redisCommand *base_cmd = dictFetchValue(commands, argv[0]->ptr);
int has_subcommands = base_cmd && base_cmd->subcommands_dict;
if (argc == 1 || !has_subcommands) {
if (strict && argc != 1)
return NULL;
/* Note: It is possible that base_cmd->proc==NULL (e.g. CONFIG) */
return base_cmd;
} else { /* argc > 1 && has_subcommands */
if (strict && argc != 2)
return NULL;
/* Note: Currently we support just one level of subcommands */
return lookupSubcommand(base_cmd, argv[1]->ptr);
}
}
找不到命令就报错
七.存到假客户端上
fakeClient->cmd = fakeClient->lastcmd = cmd;
当前的命令并不是EXEC命令,那么就将命令加入到事务队列中
if (fakeClient->flags & CLIENT_MULTI &&
fakeClient->cmd->proc != execCommand)
{
queueMultiCommand(fakeClient, cmd->flags);
} else {
cmd->proc(fakeClient);
}
serverAssert(fakeClient->bufpos == 0 &&
listLength(fakeClient->reply) == 0);
验证客户端的输出缓冲区与回复链表是否为空
serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0);
判断客户端是否被阻塞,如果被阻塞
freeClientArgv(fakeClient);
if (server.aof_load_truncated) valid_up_to = ftello(fp);
如果Redis服务器发现AOF文件的加载被截断,那么将valid_up_to设定为当前文件的偏移量
if (server.key_load_delay)
debugDelay(server.key_load_delay);
}
if (fakeClient->flags & CLIENT_MULTI) {
serverLog(LL_WARNING,
"Revert incomplete MULTI/EXEC transaction in AOF file %s", filename);
valid_up_to = valid_before_multi;
假客户端是否处于一个MULTI/EXEC事务之中
goto uxeof;
}