Redis学习(二):config rewrite

工作要求,需要了解config rewrite的功能,先介绍功能。

CONFIG REWRITE 命令对启动 Redis 服务器时所指定的 redis.conf 文件进行改写: 因为 CONFIG_SET
命令可以对服务器的当前配置进行修改, 而修改后的配置可能和 redis.conf 文件中所描述的配置不一样, CONFIG REWRITE
的作用就是通过尽可能少的修改, 将服务器当前所使用的配置记录到 redis.conf 文件中。

重写会以非常保守的方式进行:

  • 原有 redis.conf 文件的整体结构和注释会被尽可能地保留。
  • 如果一个选项已经存在于原有 redis.conf 文件中 , 那么对该选项的重写会在选项原本所在的位置(行号)上进行。
  • 如果一个选项不存在于原有 redis.conf 文件中, 并且该选项被设置为默认值, 那么重写程序不会将这个选项添加到重写后的 redis.conf 文件中。
  • 如果一个选项不存在于原有 redis.conf 文件中, 并且该选项被设置为非默认值, 那么这个选项将被添加到重写后的 redis.conf 文件的末尾。
  • 未使用的行会被留白。 比如说, 如果你在原有 redis.conf 文件上设置了数个关于 save 选项的参数, 但现在你将这些 save 参数的一个或全部都关闭了, 那么这些不再使用的参数原本所在的行就会变成空白的。

即使启动服务器时所指定的 redis.conf 文件已经不再存在, CONFIG REWRITE 命令也可以重新构建并生成出一个新的
redis.conf 文件。

另一方面, 如果启动服务器时没有载入 redis.conf 文件, 那么执行 CONFIG REWRITE 命令将引发一个错误。

以下是redis3.0中关于config rewrite的源码分析:

首先是结构体

struct rewriteConfigState {
    dict *option_to_line;	//建立配置选项和所在行的映射关系
    dict *rewritten;		//用于一些在运行是取消的配置选项,需要在文件中清空
    int numlines;			//行数
    sds *lines;				//下标为存入的行位置,值为存入的配置选项
    int has_tail;			//rewrite是否对配置文件进行过更新
};

这是rewrite的主流程:

int rewriteConfig(char *path) {
    struct rewriteConfigState *state;
    sds newcontent;
    int retval;

    /* Step 1: 读取文件信息 */
    if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1;

    /* Step 2: rewrite every single option, replacing or appending it inside
     * the rewrite state. */
	
	//中间代码省略,是将Redis中所有的配置信息进行导入和更新

    /* Step 3: 删除多余的行,如有些选项值为空了,这时会将多余的行进行清空 */
    rewriteConfigRemoveOrphaned(state);

    /* Step 4: 将修改的信息写入文件中,为了保持原子性所以需要一次性写入 */
    newcontent = rewriteConfigGetContentFromState(state);
    retval = rewriteConfigOverwriteFile(server.configfile,newcontent);

    sdsfree(newcontent);
    rewriteConfigReleaseState(state);
    return retval;
}

rewriteConfigReadOldFile是读取配置文件信息:

struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
    FILE *fp = fopen(path,"r");
    struct rewriteConfigState *state = zmalloc(sizeof(*state));
    char buf[REDIS_CONFIGLINE_MAX+1];
    int linenum = -1;//因为要从0开始所以设置初始状态为1

    if (fp == NULL && errno != ENOENT) return NULL;

    state->option_to_line = dictCreate(&optionToLineDictType,NULL);
    state->rewritten = dictCreate(&optionSetDictType,NULL);
    state->numlines = 0;
    state->lines = NULL;
    state->has_tail = 0;
    if (fp == NULL) return state;

    /* Read the old file line by line, populate the state. */
    while(fgets(buf,REDIS_CONFIGLINE_MAX+1,fp) != NULL) {//读取一行数据
        int argc;
        sds *argv;
        sds line = sdstrim(sdsnew(buf),"\r\n\t ");

        linenum++; /* Zero based, so we init at -1 */

        /* Handle comments and empty lines. */
        if (line[0] == '#' || line[0] == '\0') {
        	//如果文件中有"# Generated by CONFIG REWRITE"说明曾经被rewrite修改过
            if (!state->has_tail && !strcmp(line,REDIS_CONFIG_REWRITE_SIGNATURE))
                state->has_tail = 1;
            //将空信息或注释信息写入,因为需要保留原文件注释
            rewriteConfigAppendLine(state,line);
            continue;
        }

        /* Not a comment, split into arguments. */
        argv = sdssplitargs(line,&argc);
        if (argv == NULL) {//解析有误
            /* Apparently the line is unparsable for some reason, for
             * instance it may have unbalanced quotes. Load it as a
             * comment. */
            sds aux = sdsnew("# ??? ");
            aux = sdscatsds(aux,line);
            sdsfree(line);
            rewriteConfigAppendLine(state,aux);
            continue;
        }
		//获取选项
        sdstolower(argv[0]); /* We only want lowercase config directives. */

        /* Now we populate the state according to the content of this line.
         * Append the line and populate the option -> line numbers map. */
        //建立行和行信息的映射
        rewriteConfigAppendLine(state,line);
        //建立行和选项的映射
        rewriteConfigAddLineNumberToOption(state,argv[0],linenum);

        sdsfreesplitres(argv,argc);
    }
    fclose(fp);
    return state;
}

rewriteConfigRewriteLine是将行信息更新

void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force) {
    sds o = sdsnew(option);
    list *l = dictFetchValue(state->option_to_line,o);

    rewriteConfigMarkAsProcessed(state,option);//将option写入rewritten中
	
	//如果原文件中不存在且为默认值,则不进行更新
    if (!l && !force) {
        /* Option not used previously, and we are not forced to use it. */
        sdsfree(line);
        sdsfree(o);
        return;
    }

	//如果原文件存在则将行信息更新
    if (l) {
        listNode *ln = listFirst(l);
        int linenum = (long) ln->value;

        /* There are still lines in the old configuration file we can reuse
         * for this option. Replace the line with the new one. */
        listDelNode(l,ln);
        if (listLength(l) == 0) dictDelete(state->option_to_line,o);
        sdsfree(state->lines[linenum]);
        state->lines[linenum] = line;
    } else {
        /* Append a new line. */
        //否则新增一行,如果之前未被rewrite更新过,增加注释信息
        if (!state->has_tail) {
            rewriteConfigAppendLine(state,
                sdsnew(REDIS_CONFIG_REWRITE_SIGNATURE));
            state->has_tail = 1;
        }
        rewriteConfigAppendLine(state,line);
    }
    sdsfree(o);
}

rewriteConfigRemoveOrphaned是将rewritten中的option清空,因为这些已经不需要了

void rewriteConfigRemoveOrphaned(struct rewriteConfigState *state) {
    dictIterator *di = dictGetIterator(state->option_to_line);
    dictEntry *de;

    while((de = dictNext(di)) != NULL) {
        list *l = dictGetVal(de);
        sds option = dictGetKey(de);

        /* Don't blank lines about options the rewrite process
         * don't understand. */
		//如果rewritten中没有option说明,如果没有说明他是不可在运行时动态修改的选项
        if (dictFind(state->rewritten,option) == NULL) {
            redisLog(REDIS_DEBUG,"Not rewritten option: %s", option);
            continue;
        }
		//option_to_line中的选项都是多余的,清空他所在的行
        while(listLength(l)) {
            listNode *ln = listFirst(l);
            int linenum = (long) ln->value;

            sdsfree(state->lines[linenum]);
            state->lines[linenum] = sdsempty();
            listDelNode(l,ln);
        }
    }
    dictReleaseIterator(di);
}

rewriteConfigGetContentFromState是将lines中的信息写入到一个sds中

sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
    sds content = sdsempty();
    int j, was_empty = 0;

    for (j = 0; j < state->numlines; j++) {
        /* Every cluster of empty lines is turned into a single empty line. */
        //将多行空行合并为一行
        if (sdslen(state->lines[j]) == 0) {
            if (was_empty) continue;
            was_empty = 1;
        } else {
            was_empty = 0;
        }
        content = sdscatsds(content,state->lines[j]);
        content = sdscatlen(content,"\n",1);
    }
    return content;
}

rewriteConfigOverwriteFile将数据写入文件中

这个实现考虑了一种情况,就是重新写入的文件大小比原文件小。这时候需要把文件后面多出的部分先用#替换,然后将文本写入,最后再将文件截断。那时候我就想为什么要多此一举呢。根据Redis提供的注释是考虑了一种特殊情况,在写入新文件之前如果宕机了,就会造成文件错乱,所以先用#注释再进行写会比较安全。

int rewriteConfigOverwriteFile(char *configfile, sds content) {
    int retval = 0;
    int fd = open(configfile,O_RDWR|O_CREAT,0644);
    int content_size = sdslen(content), padding = 0;
    struct stat sb;
    sds content_padded;

    /* 1) Open the old file (or create a new one if it does not
     *    exist), get the size. */
    if (fd == -1) return -1; /* errno set by open(). */
    if (fstat(fd,&sb) == -1) {
        close(fd);
        return -1; /* errno set by fstat(). */
    }

    /* 2) Pad the content at least match the old file size. */
    content_padded = sdsdup(content);
    if (content_size < sb.st_size) {
        /* If the old file was bigger, pad the content with
         * a newline plus as many "#" chars as required. */
        padding = sb.st_size - content_size;
        content_padded = sdsgrowzero(content_padded,sb.st_size);
        content_padded[content_size] = '\n';
        memset(content_padded+content_size+1,'#',padding-1);
    }

    /* 3) Write the new content using a single write(2). */
    if (write(fd,content_padded,strlen(content_padded)) == -1) {
        retval = -1;
        goto cleanup;
    }

    /* 4) Truncate the file to the right length if we used padding. */
    if (padding) {
        if (ftruncate(fd,content_size) == -1) {
            /* Non critical error... */
        }
    }

cleanup:
    sdsfree(content_padded);
    close(fd);
    return retval;
}

核心函数就这些,现在网络上好像没有这方面的源码解读,所以我看了挺久的,这方面也比较偏,写的还是有点粗糙的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值