工作要求,需要了解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;
}
核心函数就这些,现在网络上好像没有这方面的源码解读,所以我看了挺久的,这方面也比较偏,写的还是有点粗糙的。