Redis(REmote DIctionary Server)是一个开源的内存数据库系统,用于存储键值对的数据。在Redis的源代码中,config.c 文件是负责处理配置相关操作的文件。这个文件包含了一些与配置有关的函数和逻辑。
具体而言,config.c 文件可能包含以下一些功能:
- 解析配置文件: Redis通常使用一个配置文件来设置服务器的参数。config.c 可能包含用于解析这个配置文件的函数,将配置文件中的配置项读取到内存中。
- 处理配置参数: 一旦配置文件被解析,config.c 可能包含用于处理和验证配置参数的函数。这包括检查参数的有效性、设置相应的内存结构等。
- 运行时配置: Redis允许在运行时修改一些配置参数,而不需要重新启动服务器。config.c 可能包含用于处理这种动态配置的函数。
- 配置持久化: Redis支持将配置持久化到磁盘,以便在服务器重新启动时恢复。config.c 可能包含与将配置写入和从磁盘加载的相关逻辑。
- 配置信息查询: 提供函数来查询当前服务器的配置信息,使用户或其他部分能够获取有关服务器配置的信息。
//用于存储不需要特殊处理的配置值。这些配置值可以通过设置、获取、加载或重写而无需进行额外的特殊处理。
typedef struct boolConfigData {
int *config; /* The pointer to the server config this value is stored in *///指向服务器配置中存储此值的指针。
const int default_value; /* The default value of the config on rewrite *///配置的默认值,在重写时使用。
//可选函数指针,用于检查新值的有效性。这个函数在设置新值时可以进行调用,以确保新值是有效的。参数 val 是要验证的新值,err 是一个指向错误字符串指针的指针,用于返回验证失败时的错误信息。
int (*is_valid_fn)(int val, char **err); /* Optional function to check validity of new value (generic doc above) */
//可选函数指针,用于在运行时应用新值。这个函数在配置值发生变化时调用,以在运行时应用新的配置值。参数 val 是新的配置值,prev 是先前的配置值,err 是一个指向错误字符串指针的指针,用于返回应用新值时的错误信息。
int (*update_fn)(int val, int prev, char **err); /* Optional function to apply new value at runtime (generic doc above) */
} boolConfigData;
//用于存储需要特殊处理的字符串类型的配置值。
typedef struct stringConfigData {
char **config; /* Pointer to the server config this value is stored in. *///指向服务器配置中存储此值的指针。
const char *default_value; /* Default value of the config on rewrite. */
int (*is_valid_fn)(char* val, char **err); /* Optional function to check validity of new value (generic doc above) */
int (*update_fn)(char* val, char* prev, char **err); /* Optional function to apply new value at runtime (generic doc above) */
int convert_empty_to_null; /* Boolean indicating if empty strings should
be stored as a NULL value. *///布尔值,指示是否将空字符串存储为 NULL 值。
} stringConfigData;
typedef struct enumConfigData {
int *config; /* The pointer to the server config this value is stored in */
configEnum *enum_value; /* The underlying enum type this data represents */
const int default_value; /* The default value of the config on rewrite */
int (*is_valid_fn)(int val, char **err); /* Optional function to check validity of new value (generic doc above) */
int (*update_fn)(int val, int prev, char **err); /* Optional function to apply new value at runtime (generic doc above) */
} enumConfigData;
typedef enum numericType {
NUMERIC_TYPE_INT,
NUMERIC_TYPE_UINT,
NUMERIC_TYPE_LONG,
NUMERIC_TYPE_ULONG,
NUMERIC_TYPE_LONG_LONG,
NUMERIC_TYPE_ULONG_LONG,
NUMERIC_TYPE_SIZE_T,
NUMERIC_TYPE_SSIZE_T,
NUMERIC_TYPE_OFF_T,
NUMERIC_TYPE_TIME_T,
} numericType;
/*这个结构体的设计旨在提供一种通用的方式来处理不同类型的数值配置。它允许通过指定数值类型和边界条件,
以及提供自定义的验证和更新函数,来确保在配置处理中能够正确地设置和更新各种数值配置。*/
typedef struct numericConfigData {
/*一个联合体,包含指向不同类型数值配置的指针,具体类型由 numeric_type 决定。
这里包括整数、无符号整数、长整数、无符号长整数、长长整数、无符号长长整数、size_t、ssize_t、off_t 和 time_t。*/
union {
int *i;
unsigned int *ui;
long *l;
unsigned long *ul;
long long *ll;
unsigned long long *ull;
size_t *st;
ssize_t *sst;
off_t *ot;
time_t *tt;
} config; /* The pointer to the numeric config this value is stored in */
int is_memory; /* Indicates if this value can be loaded as a memory value *//*表示该值是否可以被加载为内存值。*/
numericType numeric_type; /* An enum indicating the type of this value *//*个枚举类型 numericType,表示该值的数值类型。*/
long long lower_bound; /* The lower bound of this numeric value *//*该数值的下限。*/
long long upper_bound; /* The upper bound of this numeric value *//*该数值的上限。*/
const long long default_value; /* The default value of the config on rewrite *///该数值的默认值,用于在重写配置时恢复默认值。
int (*is_valid_fn)(long long val, char **err); /* Optional function to check validity of new value (generic doc above) *///一个可选的函数指针,用于检查新值的有效性。
int (*update_fn)(long long val, long long prev, char **err); /* Optional function to apply new value at runtime (generic doc above) *///一个可选的函数指针,用于在运行时应用新值。
} numericConfigData;
/*这样的设计允许在一个联合体中灵活地存储不同类型的配置数据,
具体使用哪个类型取决于配置的性质。这种结构使得配置系统能够更方便地处理各种不同类型的配置参数。*/
typedef union typeData {
boolConfigData yesno;
stringConfigData string;
enumConfigData enumd;
numericConfigData numeric;
} typeData;
//这段代码定义了一个结构体 typeInterface,该结构体包含了一组函数指针,用于处理不同类型的配置数据。
typedef struct typeInterface {
/* Called on server start, to init the server with default value */
void (*init)(typeData data);//在服务器启动时调用,用于使用默认值初始化服务器。
/* Called on server start, should return 1 on success, 0 on error and should set err */
//在服务器启动时调用,应在成功时返回1,出现错误时返回0,并且可能设置错误信息。用于加载配置数据。
int (*load)(typeData data, sds *argc, int argv, char **err);
/* Called on server startup and CONFIG SET, returns 1 on success, 0 on error
* and can set a verbose err string, update is true when called from CONFIG SET */
//在服务器启动和执行 CONFIG SET 命令时调用,用于设置配置数据。在成功时返回1,出现错误时返回0,并且可能设置错误信息。update 参数指示是否来自 CONFIG SET 命令。
int (*set)(typeData data, sds value, int update, char **err);
/* Called on CONFIG GET, required to add output to the client */
//在执行 CONFIG GET 命令时调用,用于将配置数据的信息添加到客户端。
void (*get)(client *c, typeData data);
/* Called on CONFIG REWRITE, required to rewrite the config state */
//在执行 CONFIG REWRITE 命令时调用,用于重写配置状态。
void (*rewrite)(typeData data, const char *name, struct rewriteConfigState *state);
} typeInterface;
typedef struct standardConfig {
const char *name; /* The user visible name of this config *///标准配置的用户可见名称。
const char *alias; /* An alias that can also be used for this config *///标准配置的别名,也可以用于引用该配置。
const int modifiable; /* Can this value be updated by CONFIG SET? *///表示该配置项是否可以通过 CONFIG SET 命令进行修改。这是一个标志,为1表示可以修改,为0表示不可修改。
//一个 typeInterface 类型的字段,包含了一组函数指针,定义了该配置项的类型接口,用于处理配置的初始化、加载、设置、获取和重写。
typeInterface interface; /* The function pointers that define the type interface */
//包含类型特定的数据,这些数据将在类型接口函数中使用。
typeData data; /* The type specific data exposed used by the interface */
} standardConfig;
使用结构体typeData将所使用的数据类型全部包含,使用typeInterface结构体来定义对这些配置的操作的相关函数。通过上述两个结构体加上配置的姓名和别名,以及是否能够修改等操作定义了一个配置的结构体类型。
void loadServerConfigFromString(char *config) {
char *err = NULL;
int linenum = 0, totlines, i;
int slaveof_linenum = 0;
sds *lines;
// 将配置文件内容按行切分
lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
// 逐行处理配置指令
for (i = 0; i < totlines; i++) {
sds *argv;
int argc;
linenum = i+1;
lines[i] = sdstrim(lines[i]," \t\r\n");
/* Skip comments and blank lines */
if (lines[i][0] == '#' || lines[i][0] == '\0') continue;
/* Split into arguments */
argv = sdssplitargs(lines[i],&argc);
if (argv == NULL) {
err = "Unbalanced quotes in configuration line";
goto loaderr;
}
/* Skip this line if the resulting command vector is empty. */
if (argc == 0) {
sdsfreesplitres(argv,argc);
continue;
}
sdstolower(argv[0]);
/* Iterate the configs that are standard */
int match = 0;
for (standardConfig *config = configs; config->name != NULL; config++) {
if ((!strcasecmp(argv[0],config->name) ||
(config->alias && !strcasecmp(argv[0],config->alias))))
{
if (argc != 2) {
err = "wrong number of arguments";
goto loaderr;
}
if (!config->interface.set(config->data, argv[1], 0, &err)) {
goto loaderr;
}
match = 1;
break;
}
}
if (match) {
sdsfreesplitres(argv,argc);
continue;
}
/* Execute config directives */
if (!strcasecmp(argv[0],"bind") && argc >= 2) {
int j, addresses = argc-1;
if (addresses > CONFIG_BINDADDR_MAX) {
err = "Too many bind addresses specified"; goto loaderr;
}
/* Free old bind addresses */
for (j = 0; j < server.bindaddr_count; j++) {
zfree(server.bindaddr[j]);
}
for (j = 0; j < addresses; j++)
server.bindaddr[j] = zstrdup(argv[j+1]);
server.bindaddr_count = addresses;
} else if (!strcasecmp(argv[0],"unixsocketperm") && argc == 2) {
errno = 0;
server.unixsocketperm = (mode_t)strtol(argv[1], NULL, 8);
if (errno || server.unixsocketperm > 0777) {
err = "Invalid socket file permissions"; goto loaderr;
}
} 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();
}
} else if (!strcasecmp(argv[0],"dir") && argc == 2) {
if (chdir(argv[1]) == -1) {
serverLog(LL_WARNING,"Can't chdir to '%s': %s",
argv[1], strerror(errno));
exit(1);
}
} else if (!strcasecmp(argv[0],"logfile") && argc == 2) {
FILE *logfp;
zfree(server.logfile);
server.logfile = zstrdup(argv[1]);
if (server.logfile[0] != '\0') {
/* Test if we are able to open the file. The server will not
* be able to abort just for this problem later... */
logfp = fopen(server.logfile,"a");
if (logfp == NULL) {
err = sdscatprintf(sdsempty(),
"Can't open the log file: %s", strerror(errno));
goto loaderr;
}
fclose(logfp);
}
} else if (!strcasecmp(argv[0],"include") && argc == 2) {
loadServerConfig(argv[1],NULL);
} else if ((!strcasecmp(argv[0],"client-query-buffer-limit")) && argc == 2) {
server.client_max_querybuf_len = memtoll(argv[1],NULL);
} else if ((!strcasecmp(argv[0],"slaveof") ||
!strcasecmp(argv[0],"replicaof")) && argc == 3) {
slaveof_linenum = linenum;
server.masterhost = sdsnew(argv[1]);
server.masterport = atoi(argv[2]);
server.repl_state = REPL_STATE_CONNECT;
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
goto loaderr;
}
/* The old "requirepass" directive just translates to setting
* a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1);
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
sdsfree(server.requirepass);
server.requirepass = sdsnew(argv[1]);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"rename-command") && argc == 3) {
struct redisCommand *cmd = lookupCommand(argv[1]);
int retval;
if (!cmd) {
err = "No such command in rename-command";
goto loaderr;
}
/* If the target command name is the empty string we just
* remove it from the command table. */
retval = dictDelete(server.commands, argv[1]);
serverAssert(retval == DICT_OK);
/* Otherwise we re-add the command under a different name. */
if (sdslen(argv[2]) != 0) {
sds copy = sdsdup(argv[2]);
retval = dictAdd(server.commands, copy, cmd);
if (retval != DICT_OK) {
sdsfree(copy);
err = "Target command name already exists"; goto loaderr;
}
}
} else if (!strcasecmp(argv[0],"cluster-config-file") && argc == 2) {
zfree(server.cluster_configfile);
server.cluster_configfile = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"client-output-buffer-limit") &&
argc == 5)
{
int class = getClientTypeByName(argv[1]);
unsigned long long hard, soft;
int soft_seconds;
if (class == -1 || class == CLIENT_TYPE_MASTER) {
err = "Unrecognized client limit class: the user specified "
"an invalid one, or 'master' which has no buffer limits.";
goto loaderr;
}
hard = memtoll(argv[2],NULL);
soft = memtoll(argv[3],NULL);
soft_seconds = atoi(argv[4]);
if (soft_seconds < 0) {
err = "Negative number of seconds in soft limit is invalid";
goto loaderr;
}
server.client_obuf_limits[class].hard_limit_bytes = hard;
server.client_obuf_limits[class].soft_limit_bytes = soft;
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
} else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
int flags = keyspaceEventsStringToFlags(argv[1]);
if (flags == -1) {
err = "Invalid event class character. Use 'g$lshzxeA'.";
goto loaderr;
}
server.notify_keyspace_events = flags;
} else if (!strcasecmp(argv[0],"user") && argc >= 2) {
int argc_err;
if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
char buf[1024];
char *errmsg = ACLSetUserStringError();
snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s",
argv[argc_err],errmsg);
err = buf;
goto loaderr;
}
} else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
queueLoadModule(argv[1],&argv[2],argc-2);
} else if (!strcasecmp(argv[0],"sentinel")) {
/* argc == 1 is handled by main() as we need to enter the sentinel
* mode ASAP. */
if (argc != 1) {
if (!server.sentinel_mode) {
err = "sentinel directive while not in sentinel mode";
goto loaderr;
}
err = sentinelHandleConfiguration(argv+1,argc-1);
if (err) goto loaderr;
}
} else {
err = "Bad directive or wrong number of arguments"; goto loaderr;
}
sdsfreesplitres(argv,argc);
}
/* Sanity checks. */
if (server.cluster_enabled && server.masterhost) {
linenum = slaveof_linenum;
i = linenum-1;
err = "replicaof directive not allowed in cluster mode";
goto loaderr;
}
sdsfreesplitres(lines,totlines);
return;
loaderr:
// 输出错误信息并退出
fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n",
REDIS_VERSION);
fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);
fprintf(stderr, ">>> '%s'\n", lines[i]);
fprintf(stderr, "%s\n", err);
exit(1);
}
该函数是一个用于从字符串加载服务器配置的函数,通常用于读取Redis服务器的配置文件。函数的主要任务是解析配置文件中的每一行,并根据配置指令执行相应的操作。
void configSetCommand(client *c) {
robj *o;
long long ll;
int err;
char *errstr = NULL;
/*获取命令中的配置项(c->argv[2])和配置值(c->argv[3])。
使用 sdsEncodedObject 确保配置项和配置值是 SDS 编码的字符串。*/
serverAssertWithInfo(c,c->argv[2],sdsEncodedObject(c->argv[2]));
serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
o = c->argv[3];
/* Iterate the configs that are standard */
/*
遍历预定义的标准配置项数组 configs,找到与命令中给定的配置项名称或别名匹配的项。
如果找到匹配的项,调用相应的配置项处理函数进行设置,并返回 OK 响应。
*/
for (standardConfig *config = configs; config->name != NULL; config++) {
if(config->modifiable && (!strcasecmp(c->argv[2]->ptr,config->name) ||
(config->alias && !strcasecmp(c->argv[2]->ptr,config->alias))))
{
if (!config->interface.set(config->data,o->ptr,1,&errstr)) {
goto badfmt;
}
addReply(c,shared.ok);
return;
}
}
/*开始一个虚拟的条件块,实际上是一个占位符,始终不会执行。这样做是为了在条件语句中使用宏而不引入额外的代码块。*/
if (0) { /* this starts the config_set macros else-if chain. */
/* Special fields that can't be handled with general macros. */
/*处理一些特殊的配置项,例如 "requirepass"、"save"、"dir"、"client-output-buffer-limit"、"notify-keyspace-events"、"watchdog-period"、"client-query-buffer-limit" 等。
这些特殊配置项可能需要特殊的处理逻辑,例如设置密码、设置持久化参数、改变工作目录等。*/
config_set_special_field("requirepass") {
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
/* The old "requirepass" directive just translates to setting
* a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1);
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)o->ptr);
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
sdsfree(server.requirepass);
server.requirepass = sdsnew(o->ptr);
} config_set_special_field("save") {
int vlen, j;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
/* Perform sanity check before setting the new config:
* - Even number of args
* - Seconds >= 1, changes >= 0 */
if (vlen & 1) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
for (j = 0; j < vlen; j++) {
char *eptr;
long val;
val = strtoll(v[j], &eptr, 10);
if (eptr[0] != '\0' ||
((j & 1) == 0 && val < 1) ||
((j & 1) == 1 && val < 0)) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
}
/* Finally set the new config */
resetServerSaveParams();
for (j = 0; j < vlen; j += 2) {
time_t seconds;
int changes;
seconds = strtoll(v[j],NULL,10);
changes = strtoll(v[j+1],NULL,10);
appendServerSaveParams(seconds, changes);
}
sdsfreesplitres(v,vlen);
} config_set_special_field("dir") {
if (chdir((char*)o->ptr) == -1) {
addReplyErrorFormat(c,"Changing directory: %s", strerror(errno));
return;
}
} config_set_special_field("client-output-buffer-limit") {
int vlen, j;
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
/* We need a multiple of 4: <class> <hard> <soft> <soft_seconds> */
if (vlen % 4) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
/* Sanity check of single arguments, so that we either refuse the
* whole configuration string or accept it all, even if a single
* error in a single client class is present. */
for (j = 0; j < vlen; j++) {
long val;
if ((j % 4) == 0) {
int class = getClientTypeByName(v[j]);
if (class == -1 || class == CLIENT_TYPE_MASTER) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
} else {
val = memtoll(v[j], &err);
if (err || val < 0) {
sdsfreesplitres(v,vlen);
goto badfmt;
}
}
}
/* Finally set the new config */
for (j = 0; j < vlen; j += 4) {
int class;
unsigned long long hard, soft;
int soft_seconds;
class = getClientTypeByName(v[j]);
hard = memtoll(v[j+1],NULL);
soft = memtoll(v[j+2],NULL);
soft_seconds = strtoll(v[j+3],NULL,10);
server.client_obuf_limits[class].hard_limit_bytes = hard;
server.client_obuf_limits[class].soft_limit_bytes = soft;
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
}
sdsfreesplitres(v,vlen);
} config_set_special_field("notify-keyspace-events") {
int flags = keyspaceEventsStringToFlags(o->ptr);
if (flags == -1) goto badfmt;
server.notify_keyspace_events = flags;
/* Numerical fields.
* config_set_numerical_field(name,var,min,max) */
} config_set_numerical_field(
"watchdog-period",ll,0,INT_MAX) {
if (ll)
enableWatchdog(ll);
else
disableWatchdog();
/* Memory fields.
* config_set_memory_field(name,var) */
} config_set_memory_field(
"client-query-buffer-limit",server.client_max_querybuf_len) {
/* Everything else is an error... */
} config_set_else {/*如果配置项不匹配任何已知的处理逻辑,展开到这个分支。返回错误信息,表示不支持的配置参数。*/
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)c->argv[2]->ptr);
return;
}
/* On success we just return a generic OK for all the options. */
addReply(c,shared.ok);
return;
badfmt: /* Bad format errors */
if (errstr) {
addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s' - %s",
(char*)o->ptr,
(char*)c->argv[2]->ptr,
errstr);
} else {
addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
(char*)o->ptr,
(char*)c->argv[2]->ptr);
}
}
这段代码实现了 Redis 的 CONFIG SET 命令的处理逻辑。CONFIG SET 命令用于动态地设置 Redis 服务器的配置参数。
void configGetCommand(client *c) {
/*通过 addReplyDeferredLen 创建一个延迟回复长度的回复对象,用于后续动态添加元素。
获取配置项模式(pattern)和一个空的缓冲区(buf)。*/
robj *o = c->argv[2];
void *replylen = addReplyDeferredLen(c);
char *pattern = o->ptr;
char buf[128];
int matches = 0;
serverAssertWithInfo(c,o,sdsEncodedObject(o));
/* Iterate the configs that are standard */
/*使用 for 循环遍历预定义的标准配置项数组 configs。对于每个配置项和其别名,如果与指定的模式匹配,将配置项名称和对应的值回复给客户端。*/
for (standardConfig *config = configs; config->name != NULL; config++) {
if (stringmatch(pattern,config->name,1)) {
addReplyBulkCString(c,config->name);
config->interface.get(c,config->data);
matches++;
}
if (config->alias && stringmatch(pattern,config->alias,1)) {
addReplyBulkCString(c,config->alias);
config->interface.get(c,config->data);
matches++;
}
}
/*使用预定义的宏 config_get_string_field 和 config_get_numerical_field 获取字符串类型和数值类型的配置项。
如果配置项名称匹配指定的模式,将配置项名称和值回复给客户端。*/
/* String values */
config_get_string_field("logfile",server.logfile);
/* Numerical values */
config_get_numerical_field("client-query-buffer-limit",server.client_max_querybuf_len);
config_get_numerical_field("watchdog-period",server.watchdog_period);
/* Everything we can't handle with macros follows. */
/*
处理一些无法使用宏处理的特殊配置项,
例如 "dir"、"save"、"client-output-buffer-limit"、"unixsocketperm"、"slaveof"、"replicaof"、"notify-keyspace-events"、"bind"、"requirepass"。
根据配置项名称,获取相应的配置值,并回复给客户端。
*/
if (stringmatch(pattern,"dir",1)) {
char buf[1024];
if (getcwd(buf,sizeof(buf)) == NULL)
buf[0] = '\0';
addReplyBulkCString(c,"dir");
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"save",1)) {
sds buf = sdsempty();
int j;
for (j = 0; j < server.saveparamslen; j++) {
buf = sdscatprintf(buf,"%jd %d",
(intmax_t)server.saveparams[j].seconds,
server.saveparams[j].changes);
if (j != server.saveparamslen-1)
buf = sdscatlen(buf," ",1);
}
addReplyBulkCString(c,"save");
addReplyBulkCString(c,buf);
sdsfree(buf);
matches++;
}
if (stringmatch(pattern,"client-output-buffer-limit",1)) {
sds buf = sdsempty();
int j;
for (j = 0; j < CLIENT_TYPE_OBUF_COUNT; j++) {
buf = sdscatprintf(buf,"%s %llu %llu %ld",
getClientTypeName(j),
server.client_obuf_limits[j].hard_limit_bytes,
server.client_obuf_limits[j].soft_limit_bytes,
(long) server.client_obuf_limits[j].soft_limit_seconds);
if (j != CLIENT_TYPE_OBUF_COUNT-1)
buf = sdscatlen(buf," ",1);
}
addReplyBulkCString(c,"client-output-buffer-limit");
addReplyBulkCString(c,buf);
sdsfree(buf);
matches++;
}
if (stringmatch(pattern,"unixsocketperm",1)) {
char buf[32];
snprintf(buf,sizeof(buf),"%o",server.unixsocketperm);
addReplyBulkCString(c,"unixsocketperm");
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"slaveof",1) ||
stringmatch(pattern,"replicaof",1))
{
char *optname = stringmatch(pattern,"slaveof",1) ?
"slaveof" : "replicaof";
char buf[256];
addReplyBulkCString(c,optname);
if (server.masterhost)
snprintf(buf,sizeof(buf),"%s %d",
server.masterhost, server.masterport);
else
buf[0] = '\0';
addReplyBulkCString(c,buf);
matches++;
}
if (stringmatch(pattern,"notify-keyspace-events",1)) {
sds flags = keyspaceEventsFlagsToString(server.notify_keyspace_events);
addReplyBulkCString(c,"notify-keyspace-events");
addReplyBulkSds(c,flags);
matches++;
}
if (stringmatch(pattern,"bind",1)) {
sds aux = sdsjoin(server.bindaddr,server.bindaddr_count," ");
addReplyBulkCString(c,"bind");
addReplyBulkCString(c,aux);
sdsfree(aux);
matches++;
}
if (stringmatch(pattern,"requirepass",1)) {
addReplyBulkCString(c,"requirepass");
sds password = server.requirepass;
if (password) {
addReplyBulkCBuffer(c,password,sdslen(password));
} else {
addReplyBulkCString(c,"");
}
matches++;
}
//通过 setDeferredMapLen 设置延迟回复的长度,其中包括所有匹配的配置项的数量。
setDeferredMapLen(c,replylen,matches);
}
这段代码实现了处理 CONFIG GET 命令的逻辑。该命令用于获取指定配置项的值,并将其回复给客户端。
/*该函数用于计算键的哈希值,它接受一个指向键的指针,这里键的类型是 SDS(Simple Dynamic String,一种字符串表示)。
这个哈希函数是对键进行哈希的关键步骤,以便快速定位到存储位置。*/
uint64_t dictSdsCaseHash(const void *key);
/*该函数用于比较两个键的大小,以便在字典中进行查找和插入操作。*/
int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2);
/*该函数用于在字典中删除键值对时,对键的数据进行清理和销毁。*/
void dictSdsDestructor(void *privdata, void *val);
/*这个函数用于清理和销毁字典中值的数据,这里的值是一个列表(list)。*/
void dictListDestructor(void *privdata, void *val);
dictType optionToLineDictType = {
dictSdsCaseHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */
dictSdsDestructor, /* key destructor */
dictListDestructor /* val destructor */
};
dictType optionSetDictType = {
dictSdsCaseHash, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */
dictSdsDestructor, /* key destructor */
NULL /* val destructor */
};
/* The config rewrite state. */
struct rewriteConfigState {
/*这是一个字典,用于将配置选项映射到旧配置文件中的多个位置。键是配置选项,而值是一个列表,表示该配置选项在旧配置文件中的多个行号(位置)。*/
dict *option_to_line; /* Option -> list of config file lines map */
/*这是一个字典,用于存储已经处理过的配置选项。这可以防止对同一选项的多次处理,确保每个选项只处理一次。*/
dict *rewritten; /* Dictionary of already processed options */
/*表示当前配置文件的总行数。*/
int numlines; /* Number of lines in current config */
/*一个动态字符串(SDS)数组,表示当前配置文件的所有行。每个元素是一个 SDS 字符串,包含了配置文件的一行。*/
sds *lines; /* Current lines as an array of sds strings */
/*一个标志,表示是否已经添加了在原始配置文件中不存在的指令。这通常用于追加一些新的配置指令到配置文件尾部,以确保配置文件的新旧版本的兼容性。*/
int has_tail; /* True if we already added directives that were
not present in the original config file. */
};
上述结构体存在的目的是为了对当前程序的配置进行重写所引用的结构体rewriteConfigState。
struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
FILE *fp = fopen(path,"r");
if (fp == NULL && errno != ENOENT) return NULL;
char buf[CONFIG_MAX_LINE+1];
//定义一个变量 linenum 用于记录行号,初始值为 -1。这个值将用于标识每行在配置文件中的位置。
int linenum = -1;
struct rewriteConfigState *state = zmalloc(sizeof(*state));
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. */
//使用 fgets 函数逐行读取配置文件内容。
while(fgets(buf,CONFIG_MAX_LINE+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. */
/*
如果当前行是注释或者空行:
检查是否已经添加了未被重写的配置指令(state->has_tail 为0且当前行内容等于 REDIS_CONFIG_REWRITE_SIGNATURE)。
将 state->has_tail 设置为1,表示已经添加了未被重写的配置指令。
将当前行添加到配置重写状态中,并继续下一次循环。
*/
if (line[0] == '#' || line[0] == '\0') {
if (!state->has_tail && !strcmp(line,REDIS_CONFIG_REWRITE_SIGNATURE))
state->has_tail = 1;
rewriteConfigAppendLine(state,line);
continue;
}
/* Not a comment, split into arguments. */
/*如果不是注释或者空行,使用 sdssplitargs 函数将当前行拆分为参数数组 argv,并得到参数的个数 argc。*/
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. */
/*
如果 sdssplitargs 返回空,表示当前行无法解析,可能存在引号不匹配等问题。
创建一个注释字符串 aux,以 "# ???" 开头,后接原始行内容。
将原始行内容释放,并将注释字符串添加到配置重写状态中。
继续下一次循环。
*/
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);
/* Translate options using the word "slave" to the corresponding name
* "replica", before adding such option to the config name -> lines
* mapping. */
/*
如果配置选项中包含 "slave":
创建一个空的字符串 alt。
将 "slave" 之前的部分追加到 alt。
将 "slave" 替换为 "replica"。
将 "slave" 之后的部分追加到 alt。
释放原始配置选项的内存。
将新的配置选项添加到配置重写状态中。
*/
char *p = strstr(argv[0],"slave");
if (p) {
sds alt = sdsempty();
alt = sdscatlen(alt,argv[0],p-argv[0]);;
alt = sdscatlen(alt,"replica",7);
alt = sdscatlen(alt,p+5,strlen(p+5));
sdsfree(argv[0]);
argv[0] = alt;
}
//将配置选项及其行号添加到配置重写状态的映射中。
rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
//释放分割后的参数数组的内存。
sdsfreesplitres(argv,argc);
}
fclose(fp);
return state;
}
该函数用于读取旧的配置文件,将其拆分成行,并使用这些行来填充一个新创建的配置重写状态。
void rewriteConfigRewriteLine(struct rewriteConfigState *state, const char *option, sds line, int force) {
/*使用配置选项名称创建一个 sds 对象 o。*/
sds o = sdsnew(option);
//使用 state->option_to_line 字典查找指定选项对应的行号列表。
list *l = dictFetchValue(state->option_to_line,o);
//调用 rewriteConfigMarkAsProcessed 函数,将指定选项标记为已处理。
rewriteConfigMarkAsProcessed(state,option);
//如果行号列表不存在且不需要强制更新(force 为 0),则释放 line 和 o,函数结束。
if (!l && !force) {
/* Option not used previously, and we are not forced to use it. */
sdsfree(line);
sdsfree(o);
return;
}
if (l) {
//获取行号列表中的第一个节点,并获取其对应的行号 linenum。
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]);
//将新的配置行 line 存储到原有行号对应的位置。
state->lines[linenum] = line;
} else {
/* Append a new line. */
/*如果 state->has_tail 为 0,表示之前还没有追加过行,因此追加一条包含 Redis 配置重写标记的注释行。*/
if (!state->has_tail) {
/*追加新的配置行到文件末尾。*/
rewriteConfigAppendLine(state,
sdsnew(REDIS_CONFIG_REWRITE_SIGNATURE));
/*将 state->has_tail 标记为 1,表示已经追加过至少一行了。*/
state->has_tail = 1;
}
rewriteConfigAppendLine(state,line);
}
sdsfree(o);
}
函数的目的是使用新的配置行(line)来重新写入指定的配置选项。
int rewriteConfig(char *path) {
struct rewriteConfigState *state;
sds newcontent;
int retval;
/* Step 1: read the old config into our rewrite state. */
/*使用 rewriteConfigReadOldFile 函数读取旧的配置文件,得到包含配置信息的 rewriteConfigState 结构。*/
if ((state = rewriteConfigReadOldFile(path)) == NULL) return -1;
/* Step 2: rewrite every single option, replacing or appending it inside
* the rewrite state. */
/* Iterate the configs that are standard */
/*遍历预定义的配置项数组 configs,调用每个配置项的 rewrite 函数,将配置信息更新到 rewriteConfigState 中。*/
for (standardConfig *config = configs; config->name != NULL; config++) {
config->interface.rewrite(config->data, config->name, state);
}
/*针对一些特殊的配置项(如 bind、unixsocketperm、logfile、save、user 等),调用相应的处理函数,将这些配置项的信息也更新到 rewriteConfigState 中。*/
rewriteConfigBindOption(state);
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,CONFIG_DEFAULT_UNIX_SOCKET_PERM);
rewriteConfigStringOption(state,"logfile",server.logfile,CONFIG_DEFAULT_LOGFILE);
rewriteConfigSaveOption(state);
rewriteConfigUserOption(state);
rewriteConfigDirOption(state);
rewriteConfigSlaveofOption(state,"replicaof");
rewriteConfigRequirepassOption(state,"requirepass");
rewriteConfigBytesOption(state,"client-query-buffer-limit",server.client_max_querybuf_len,PROTO_MAX_QUERYBUF_LEN);
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
rewriteConfigNotifykeyspaceeventsOption(state);
rewriteConfigClientoutputbufferlimitOption(state);
/* Rewrite Sentinel config if in Sentinel mode. */
/*如果 Redis 处于 Sentinel 模式,进一步调用 rewriteConfigSentinelOption 处理 Sentinel 的配置。*/
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
/* Step 3: remove all the orphaned lines in the old file, that is, lines
* that were used by a config option and are no longer used, like in case
* of multiple "save" options or duplicated options. */
/*通过 rewriteConfigRemoveOrphaned 函数,移除那些在重写过程中未使用的行,以确保最终的配置文件不包含无效的行。*/
rewriteConfigRemoveOrphaned(state);
/* Step 4: generate a new configuration file from the modified state
* and write it into the original file. */
/*使用 rewriteConfigGetContentFromState 函数,将更新后的配置信息从 rewriteConfigState 结构中生成为一个字符串。*/
newcontent = rewriteConfigGetContentFromState(state);
/*最后,通过 rewriteConfigOverwriteFile 函数,使用一个或多个系统调用,将新的配置内容写回原配置文件。*/
retval = rewriteConfigOverwriteFile(server.configfile,newcontent);
sdsfree(newcontent);
/*释放占用的内存,包括 rewriteConfigState 结构和生成新配置文件内容的临时字符串。*/
rewriteConfigReleaseState(state);
return retval;
}
函数的目标是以一种谨慎的方式更新配置文件,保持尽可能多的原始文件结构和注释,同时确保只有实际发生更改的配置参数才会被写入新的配置文件。
void configCommand(client *c) {
/* Only allow CONFIG GET while loading. */
/*检查服务器是否正在加载数据(server.loading),
如果是,并且用户执行的命令不是 CONFIG GET,则只允许执行 CONFIG GET 命令。这是为了防止在加载数据期间修改配置。*/
if (server.loading && strcasecmp(c->argv[1]->ptr,"get")) {
addReplyError(c,"Only CONFIG GET is allowed during loading");
return;
}
/***处理帮助命令:**如果命令参数个数为2,且第二个参数是 "help",则返回 CONFIG 命令的帮助信息,包括支持的子命令及其说明。*/
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
"GET <pattern> -- Return parameters matching the glob-like <pattern> and their values.",
"SET <parameter> <value> -- Set parameter to value.",
"RESETSTAT -- Reset statistics reported by INFO.",
"REWRITE -- Rewrite the configuration file.",
NULL
};
addReplyHelp(c, help);
/**处理 CONFIG SET 命令:**如果第一个参数是 "set",且命令参数个数为4,则调用 configSetCommand 处理 CONFIG SET 子命令。*/
} else if (!strcasecmp(c->argv[1]->ptr,"set") && c->argc == 4) {
configSetCommand(c);
/**如果第一个参数是 "get",且命令参数个数为3,则调用 configGetCommand 处理 CONFIG GET 子命令。*/
} else if (!strcasecmp(c->argv[1]->ptr,"get") && c->argc == 3) {
configGetCommand(c);
/***如果第一个参数是 "resetstat",且命令参数个数为2,则重置服务器和命令表的统计信息,并返回 "OK"。*/
} else if (!strcasecmp(c->argv[1]->ptr,"resetstat") && c->argc == 2) {
resetServerStats();
resetCommandTableStats();
addReply(c,shared.ok);
/**如果第一个参数是 "rewrite",且命令参数个数为2,则检查服务器是否有配置文件(server.configfile),如果没有,则返回错误信息。
* 否则,调用 rewriteConfig 函数重新生成配置文件。如果重写失败,则记录错误信息并返回相应的错误信息,否则返回 "OK"。*/
} else if (!strcasecmp(c->argv[1]->ptr,"rewrite") && c->argc == 2) {
if (server.configfile == NULL) {
addReplyError(c,"The server is running without a config file");
return;
}
if (rewriteConfig(server.configfile) == -1) {
serverLog(LL_WARNING,"CONFIG REWRITE failed: %s", strerror(errno));
addReplyErrorFormat(c,"Rewriting config file: %s", strerror(errno));
} else {/*如果命令不符合上述任何一种情况,则返回子命令语法错误信息。*/
serverLog(LL_WARNING,"CONFIG REWRITE executed with success.");
addReply(c,shared.ok);
}
} else {
/*总体而言,CONFIG 命令用于配置 Redis 服务器的行为和参数,通过子命令的方式支持不同的配置操作。*/
addReplySubcommandSyntaxError(c);
return;
}
}
这段代码定义了 CONFIG 命令的处理函数configCommand。该命令用于配置 Redis 服务器的参数。