redis是如何读取AOF的

关于源码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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值