关于Redis RDB 持久化认知的一个误区

众所周知,redis RDB持久化主要有两种方式:save和bgsave,其触发方式包括自动和手动。bgsave不论在何种触发方式下均通过后台进程的形式进行,对主线程阻塞相对很短,生产环境下推荐使用;而save命令会阻塞主线程直到持久化完成,生产环境下一般禁止使用。而在redis服务的redis.conf配置文件中,有关于RDB持久化,是通过“save <seconds> <changes>”的形式进行配置,问题来了,这个save是对应的save命令还是bgsave命令?这种配置形式是不是可能让人产生误解?

以redis-2.8.24源码为例,先来校验下两种命令格式:

struct redisCommand redisCommandTable[] = {
    ... 
    {"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0},
    {"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0},
    ...
};

由上面的redisCommandTable全局数组可知,save和bgsave分别对应saveCommand和bgsaveCommand,这两个命令可以望文生义。

rdb持久化的两个命令在rdb.c中实现,saveCommand实质阻塞调用rdbSave来完成,而bgsaveCommand则通过rdbSaveBackground内部fork子进程来执行rdbSave,不再贴出源码,符合逻辑,没有问题。

那我们来看redis.conf文件的读取加载,redis服务的入口main函数中调用loadServerConfig完成redis.conf的文件读取(config.c),内部调用loadServerConfigFromString:

void loadServerConfigFromString(char *config) {
    ...
    } else if (!strcasecmp(argv[0],"save")) {
        if (argc == 3) {
            int seconds = atoi(argv[1]);
            int changes = atoi(argv[2]);
            if (seconds < 1 || changes < 0) {
                err = "Invalid save parameters"; goto loaderr;
            }
            appendServerSaveParams(seconds,changes);
        } else if (argc == 2 && !strcasecmp(argv[1],"")) {
            resetServerSaveParams();
        }
    }
    ...
}

void appendServerSaveParams(time_t seconds, int changes) {
    server.saveparams = zrealloc(server.saveparams,sizeof(struct saveparam)*(server.saveparamslen+1));
    server.saveparams[server.saveparamslen].seconds = seconds;
    server.saveparams[server.saveparamslen].changes = changes;
    server.saveparamslen++;
}

读取配置文件中关于RDB持久化的配置,初始化全局的redisServer结构体。

继续跟踪redis-server执行的serverCron事件(redis.c),这里完成对redis.conf中的save配置的触发执行:

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    ...

    /* Check if a background saving or AOF rewrite in progress terminated. */
    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1) {
        int statloc;
        pid_t pid;

        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
            int exitcode = WEXITSTATUS(statloc);
            int bysignal = 0;

            if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);

            if (pid == server.rdb_child_pid) {
                backgroundSaveDoneHandler(exitcode,bysignal);
            } else if (pid == server.aof_child_pid) {
                backgroundRewriteDoneHandler(exitcode,bysignal);
            } else {
                redisLog(REDIS_WARNING,
                    "Warning, detected child with unmatched pid: %ld",
                    (long)pid);
            }
            updateDictResizePolicy();
        }
    } else {
        /* If there is not a background saving/rewrite in progress check if
         * we have to save/rewrite now */
         for (j = 0; j < server.saveparamslen; j++) {
            struct saveparam *sp = server.saveparams+j;

            /* Save if we reached the given amount of changes,
             * the given amount of seconds, and if the latest bgsave was
             * successful or if, in case of an error, at least
             * REDIS_BGSAVE_RETRY_DELAY seconds already elapsed. */
            if (server.dirty >= sp->changes &&
                server.unixtime-server.lastsave > sp->seconds &&
                (server.unixtime-server.lastbgsave_try >
                 REDIS_BGSAVE_RETRY_DELAY ||
                 server.lastbgsave_status == REDIS_OK))
            {
                redisLog(REDIS_NOTICE,"%d changes in %d seconds. Saving...",
                    sp->changes, (int)sp->seconds);
                rdbSaveBackground(server.rdb_filename);
                break;
            }
         }

         /* Trigger an AOF rewrite if needed */
         if (server.rdb_child_pid == -1 &&
             server.aof_child_pid == -1 &&
             server.aof_rewrite_perc &&
             server.aof_current_size > server.aof_rewrite_min_size)
         {
            long long base = server.aof_rewrite_base_size ?
                            server.aof_rewrite_base_size : 1;
            long long growth = (server.aof_current_size*100/base) - 100;
            if (growth >= server.aof_rewrite_perc) {
                redisLog(REDIS_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
                rewriteAppendOnlyFileBackground();
            }
         }
    }

    ...

}

由第36-46可知,达到redis.conf中配置的save触发条件,会调用rdbSaveBackground,可见,这个自动触发的save配置,实际是对应后台进程的bgsave调用。

对于开篇提出的问题,疑惑终于解开。对于redis.conf的save配置,此save非彼save,笔者觉得redis.conf配置中不如就改成bgsave,或者整个redis就直接把阻塞的save完全废弃掉吧。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注