AOF 恢复过程

AOF 的数据恢复过程设计很巧妙,它模拟一个 redis 的服务过程。redis 首先虚拟一个客户端,读取 AOF 文件恢复 redis 命令和参数;接着过程就和服务客户端一样执行命令相应的函数,从而恢复数据,这样做的目的无非是提高代码的复用率。这些过程主要在loadAppendOnlyFile() 中实现。

// 加载 AOF 文件,恢复数据
/* Replay the append log file. On error REDIS_OK is returned. On non fatal
* error (the append only file is zero-length) REDIS_ERR is returned. On
* fatal error an error message is logged and the program exists. */
int loadAppendOnlyFile(char *filename) {
    struct redisClient *fakeClient;
    FILE *fp = fopen(filename,"r");
    struct redis_stat sb;
    int old_aof_state = server.aof_state;
    long loops = 0;

    // 文件大小不能为 0
    if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
        server.aof_current_size = 0;
        fclose(fp);
        return REDIS_ERR;
    }

    if (fp == NULL) {
        redisLog(REDIS_WARNING,"Fatal error: can't open the append log file "
            "for reading: %s",strerror(errno));
        exit(1);
    }

    // 正在执行 AOF 加载操作,于是暂时禁止 AOF 的所有操作,以免混淆
    /* Temporarily disable AOF, to prevent EXEC from feeding a MULTI
     * to the same file we're about to read. */
    server.aof_state = REDIS_AOF_OFF;

    // 虚拟出一个客户端,即 redisClient
    fakeClient = createFakeClient();
    startLoading(fp);

    while(1) {
        int argc, j;
        unsigned long len;
        robj **argv;
        char buf[128];
        sds argsds;
        struct redisCommand *cmd;

        // 每循环 1000 次,在恢复数据的同时,服务器也为客户端服务。
        // aeProcessEvents() 会进入事件循环
        /* Serve the clients from time to time */
        if (!(loops++ % 1000)) {
            loadingProgress(ftello(fp));
            aeProcessEvents(server.el, AE_FILE_EVENTS|AE_DONT_WAIT);
        }

        // 可能 aof 文件到了结尾
        if (fgets(buf,sizeof(buf),fp) == NULL) {
            if (feof(fp))
                break;
            else
                goto readerr;
        }

        // 必须以“*”开头,格式不对,退出
        if (buf[0] != '*') goto fmterr;

        // 参数的个数
        argc = atoi(buf+1);

        // 参数个数错误
        if (argc < 1) goto fmterr;

        // 为参数分配空间
        argv = zmalloc(sizeof(robj*)*argc);

        // 依次读取参数
        for (j = 0; j < argc; j++) {
            if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;
            if (buf[0] != '$') goto fmterr;
            len = strtol(buf+1,NULL,10);
            argsds = sdsnewlen(NULL,len);
            if (len && fread(argsds,len,1,fp) == 0) goto fmterr;
            argv[j] = createObject(REDIS_STRING,argsds);
            if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */
        }

        // 找到相应的命令
        /* Command lookup */
        cmd = lookupCommand(argv[0]->ptr);
        if (!cmd) {
            redisLog(REDIS_WARNING,"Unknown command '%s' reading the "
                "append only file", (char*)argv[0]->ptr);
            exit(1);
        }

        // 执行命令,模拟服务客户端请求的过程,从而写入数据
        /* Run the command in the context of a fake client */
        fakeClient->argc = argc;
        fakeClient->argv = argv;
        cmd->proc(fakeClient);

        /* The fake client should not have a reply */
        redisAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply)
             == 0);
        /* The fake client should never get blocked */
        redisAssert((fakeClient->flags & REDIS_BLOCKED) == 0);

        // 释放虚拟客户端空间
        /* Clean up. Command code may have changed argv/argc so we use the
         * argv/argc of the client instead of the local variables. */
        for (j = 0; j < fakeClient->argc; j++)
            decrRefCount(fakeClient->argv[j]);
        zfree(fakeClient->argv);
    }

    /* This point can only be reached when EOF is reached without errors.
     * If the client is in the middle of a MULTI/EXEC, log error and quit. */
    if (fakeClient->flags & REDIS_MULTI) goto readerr;

    // 清理工作
    fclose(fp);
    freeFakeClient(fakeClient);

    // 恢复旧的 AOF 状态
    server.aof_state = old_aof_state;
    stopLoading();

    // 记录最近 AOF 操作的文件大小
    aofUpdateCurrentSize();
    server.aof_rewrite_base_size = server.aof_current_size;
    return REDIS_OK;

readerr:
    // 错误,清理工作
    if (feof(fp)) {
        redisLog(REDIS_WARNING,"Unexpected end of file reading the append "
            "only file");
    } else {
        redisLog(REDIS_WARNING,"Unrecoverable error reading the append only "
            "file: %s", strerror(errno));
    }
    exit(1);
fmterr:
    redisLog(REDIS_WARNING,"Bad file format reading the append only file: "
        "make a backup of your AOF file, then use ./redis-check-aof --fix "
        "<filename>");
    exit(1);
}

results matching ""

    No results matching ""