整个Redis启动过程为以下几个步骤
1.基本初始化(随机种子和时区设置)
2.哨兵模式设置,RDB,AOF检测
3.配置参数解析
4.Server初始化
5.执行EventLoop开始接受请求
整个过程都是单线程完成。
直接看server.c里面的main函数,里面就是整个redis的入口,首先会检查是不是测试,这个宏只有需要对Redis进行二开或者Debug源码的时候用到。
cint main(int argc, char **argv) {
struct timeval tv;
int j;
char config_from_stdin = 0;
#ifdef REDIS_TEST
monotonicInit(); /* Required for dict tests, that are relying on monotime during dict rehashing. */
if (argc >= 3 && !strcasecmp(argv[1], "test")) {
int flags = 0;
for (j = 3; j < argc; j++) {
char *arg = argv[j];
if (!strcasecmp(arg, "--accurate")) flags |= REDIS_TEST_ACCURATE;
else if (!strcasecmp(arg, "--large-memory")) flags |= REDIS_TEST_LARGE_MEMORY;
else if (!strcasecmp(arg, "--valgrind")) flags |= REDIS_TEST_VALGRIND;
}
if (!strcasecmp(argv[2], "all")) {
int numtests = sizeof(redisTests)/sizeof(struct redisTest);
for (j = 0; j < numtests; j++) {
redisTests[j].failed = (redisTests[j].proc(argc,argv,flags) != 0);
}
/* Report tests result */
int failed_num = 0;
for (j = 0; j < numtests; j++) {
if (redisTests[j].failed) {
failed_num++;
printf("[failed] Test - %s\n", redisTests[j].name);
} else {
printf("[ok] Test - %s\n", redisTests[j].name);
}
}
printf("%d tests, %d passed, %d failed\n", numtests,
numtests-failed_num, failed_num);
return failed_num == 0 ? 0 : 1;
} else {
redisTestProc *proc = getTestProcByName(argv[2]);
if (!proc) return -1; /* test not found */
return proc(argc,argv,flags);
}
return 0;
}
#endif
以下是对代码逻辑的分析:
首先,monotonicInit()
函数被调用,用于初始化计时器,以确保在字典重哈希期间的字典测试中使用准确的时间。
然后,检查命令行参数(argc
和argv
)是否满足特定条件,以确定是否执行测试模式。
argc
大于等于3且参数argv[1]
的值不区分大小写地等于"test",则进入测试模式的逻辑。在测试模式下,根据命令行参数的不同选项设置标志(flags
)的值。
如果参数argv[2]
的值不区分大小写地等于"all",则执行所有测试。
redisTests
数组中的每个测试,并调用相应的测试函数(redisTests[j].proc
)执行测试。redisTests[j].failed
字段中。最后,打印总体的测试结果,包括测试的总数、通过的测试数和失败的测试数。
接下来是正式启动过程
c/* We need to initialize our libraries, and the server configuration. */
#ifdef INIT_SETPROCTITLE_REPLACEMENT
spt_init(argc, argv); // 初始化进程标题的替代实现
#endif
tzset(); // 设置时区,更新 'timezone' 全局变量
zmalloc_set_oom_handler(redisOutOfMemoryHandler); // 设置内存分配失败的处理函数
gettimeofday(&tv, NULL); // 获取当前时间
srand(time(NULL) ^ getpid() ^ tv.tv_usec); // 设置随机数种子
srandom(time(NULL) ^ getpid() ^ tv.tv_usec); // 设置随机数种子
init_genrand64(((long long)tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid()); // 初始化 Mersenne Twister 随机数生成器
crc64_init(); // 初始化 CRC64 校验值计算器
// 保存当前的 umask 值,并设置新的 umask
umask(server.umask = umask(0777));
uint8_t hashseed[16];
getRandomBytes(hashseed, sizeof(hashseed)); // 生成随机字节序列
dictSetHashFunctionSeed(hashseed); // 设置字典的哈希函数种子
主要做了以下事情、
1.设置时区
2.设置内存分配失败处理函数
3.生成随机种子(用于siphash算法)
4.初始化CRC64
c char *exec_name = strrchr(argv[0], '/');
if (exec_name == NULL) exec_name = argv[0];
server.sentinel_mode = checkForSentinelMode(argc,argv, exec_name);
initServerConfig();
ACLInit(); /* The ACL subsystem must be initialized ASAP because the
basic networking code and client creation depends on it. */
moduleInitModulesSystem();
connTypeInitialize();
/* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */
server.executable = getAbsolutePath(argv[0]);
server.exec_argv = zmalloc(sizeof(char*)*(argc+1));
server.exec_argv[argc] = NULL;
初始化配置还有很多其他子系统,检查是否开启哨兵模式
c/* Returns 1 if there is --sentinel among the arguments or if
* executable name contains "redis-sentinel". */
int checkForSentinelMode(int argc, char **argv, char *exec_name) {
if (strstr(exec_name,"redis-sentinel") != NULL) return 1;
for (int j = 1; j < argc; j++)
if (!strcmp(argv[j],"--sentinel")) return 1;
return 0;
}
如果开启,初始化哨兵模式需要的配置
c if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
检查需不需要RDB和AOF持久化数据恢复
c if (strstr(exec_name,"redis-check-rdb") != NULL)
redis_check_rdb_main(argc,argv,NULL);
else if (strstr(exec_name,"redis-check-aof") != NULL)
redis_check_aof_main(argc,argv);
//*****************************************************************
int redis_check_rdb_main(int argc, char **argv, FILE *fp) {
struct timeval tv;
if (argc != 2 && fp == NULL) {
fprintf(stderr, "Usage: %s <rdb-file-name>\n", argv[0]);
exit(1);
} else if (!strcmp(argv[1],"-v") || !strcmp(argv[1], "--version")) {
sds version = checkRdbVersion();
printf("redis-check-rdb %s\n", version);
sdsfree(version);
exit(0);
}
gettimeofday(&tv, NULL);
init_genrand64(((long long) tv.tv_sec * 1000000 + tv.tv_usec) ^ getpid());
/* In order to call the loading functions we need to create the shared
* integer objects, however since this function may be called from
* an already initialized Redis instance, check if we really need to. */
if (shared.integers[0] == NULL)
createSharedObjects();
server.loading_process_events_interval_bytes = 0;
server.sanitize_dump_payload = SANITIZE_DUMP_YES;
rdbCheckMode = 1;
rdbCheckInfo("Checking RDB file %s", argv[1]);
rdbCheckSetupSignals();
int retval = redis_check_rdb(argv[1],fp);
if (retval == 0) {
rdbCheckInfo("\\o/ RDB looks OK! \\o/");
rdbShowGenericInfo();
}
if (fp) return (retval == 0) ? C_OK : C_ERR;
exit(retval);
}
//*************三种AOF文件**********************************************
input_file_type type = getInputFileType(filepath);
switch (type) {
case AOF_MULTI_PART:
checkMultiPartAof(dirpath, filepath, fix);
break;
case AOF_RESP:
checkOldStyleAof(filepath, fix, 0);
break;
case AOF_RDB_PREAMBLE:
checkOldStyleAof(filepath, fix, 1);
break;
}
接下来往下走
c
if (argc >= 2) {
j = 1; /* First option to parse in argv[] */
sds options = sdsempty();
/* Handle special options --help and --version */
if (strcmp(argv[1], "-v") == 0 ||
strcmp(argv[1], "--version") == 0) version();
if (strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "-h") == 0) usage();
if (strcmp(argv[1], "--test-memory") == 0) {
if (argc == 3) {
memtest(atoi(argv[2]),50);
exit(0);
} else {
fprintf(stderr,"Please specify the amount of memory to test in megabytes.\n");
fprintf(stderr,"Example: ./redis-server --test-memory 4096\n\n");
exit(1);
}
} if (strcmp(argv[1], "--check-system") == 0) {
exit(syscheck() ? 0 : 1);
}
/* Parse command line options
* Precedence wise, File, stdin, explicit options -- last config is the one that matters.
*
* First argument is the config file name? */
if (argv[1][0] != '-') {
/* Replace the config file in server.exec_argv with its absolute path. */
server.configfile = getAbsolutePath(argv[1]);
zfree(server.exec_argv[1]);
server.exec_argv[1] = zstrdup(server.configfile);
j = 2; // Skip this arg when parsing options
}
sds *argv_tmp;
int argc_tmp;
int handled_last_config_arg = 1;
while(j < argc) {
/* Either first or last argument - Should we read config from stdin? */
if (argv[j][0] == '-' && argv[j][1] == '\0' && (j == 1 || j == argc-1)) {
config_from_stdin = 1;
}
/* All the other options are parsed and conceptually appended to the
* configuration file. For instance --port 6380 will generate the
* string "port 6380\n" to be parsed after the actual config file
* and stdin input are parsed (if they exist).
* Only consider that if the last config has at least one argument. */
else if (handled_last_config_arg && argv[j][0] == '-' && argv[j][1] == '-') {
/* Option name */
if (sdslen(options)) options = sdscat(options,"\n");
/* argv[j]+2 for removing the preceding `--` */
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
argv_tmp = sdssplitargs(argv[j], &argc_tmp);
if (argc_tmp == 1) {
/* Means that we only have one option name, like --port or "--port " */
handled_last_config_arg = 0;
if ((j != argc-1) && argv[j+1][0] == '-' && argv[j+1][1] == '-' &&
!strcasecmp(argv[j], "--save"))
{
/* Special case: handle some things like `--save --config value`.
* In this case, if next argument starts with `--`, we will reset
* handled_last_config_arg flag and append an empty "" config value
* to the options, so it will become `--save "" --config value`.
* We are doing it to be compatible with pre 7.0 behavior (which we
* break it in #10660, 7.0.1), since there might be users who generate
* a command line from an array and when it's empty that's what they produce. */
options = sdscat(options, "\"\"");
handled_last_config_arg = 1;
}
else if ((j == argc-1) && !strcasecmp(argv[j], "--save")) {
/* Special case: when empty save is the last argument.
* In this case, we append an empty "" config value to the options,
* so it will become `--save ""` and will follow the same reset thing. */
options = sdscat(options, "\"\"");
}
else if ((j != argc-1) && argv[j+1][0] == '-' && argv[j+1][1] == '-' &&
!strcasecmp(argv[j], "--sentinel"))
{
/* Special case: handle some things like `--sentinel --config value`.
* It is a pseudo config option with no value. In this case, if next
* argument starts with `--`, we will reset handled_last_config_arg flag.
* We are doing it to be compatible with pre 7.0 behavior (which we
* break it in #10660, 7.0.1). */
options = sdscat(options, "");
handled_last_config_arg = 1;
}
else if ((j == argc-1) && !strcasecmp(argv[j], "--sentinel")) {
/* Special case: when --sentinel is the last argument.
* It is a pseudo config option with no value. In this case, do nothing.
* We are doing it to be compatible with pre 7.0 behavior (which we
* break it in #10660, 7.0.1). */
options = sdscat(options, "");
}
} else {
/* Means that we are passing both config name and it's value in the same arg,
* like "--port 6380", so we need to reset handled_last_config_arg flag. */
handled_last_config_arg = 1;
}
sdsfreesplitres(argv_tmp, argc_tmp);
} else {
/* Option argument */
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
handled_last_config_arg = 1;
}
j++;
}
loadServerConfig(server.configfile, config_from_stdin, options);
if (server.sentinel_mode) loadSentinelConfigFromQueue();
sdsfree(options);
}
这里就加载配置文件的逻辑,注意,Redis会给配置变量赋值三次
默认值
命令行
配置文件
都是互相覆盖,配置文件优先级可以说最高。
cinitServer(); // 初始化服务器
if (background || server.pidfile) createPidFile(); // 如果是后台运行或者设置了pid文件路径,则创建pid文件
if (server.set_proc_title) redisSetProcTitle(NULL); // 如果设置了进程标题,则设置Redis进程的标题
redisAsciiArt(); // 打印Redis的ASCII艺术字
checkTcpBacklogSettings(); // 检查TCP连接的backlog设置
if (server.cluster_enabled) {
clusterInit(); // 如果启用了集群模式,则进行集群初始化
}
if (!server.sentinel_mode) {
moduleInitModulesSystemLast(); // 如果不是哨兵模式,则在最后阶段初始化模块系统
moduleLoadFromQueue(); // 从模块加载队列中加载模块
}
到这里其实Redis的启动就进入尾声了,最后一个阶段就是启动EventLoop开始监听客户端链接。
c aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;
}
其中还有一段代码
c server.supervised = redisIsSupervised(server.supervised_mode);//监控模式
int background = server.daemonize && !server.supervised;
if (background) daemonize();
//***********************************************************************
void daemonize(void) {
int fd;
if (fork() != 0) exit(0); /* 父进程退出 */
setsid(); /* 创建一个新的会话 */
/* 所有输出都被重定向到 /dev/null。如果 Redis 被设置为守护进程,
* 但是配置文件中的 'logfile' 参数被设置为 'stdout',那么将不会有任何日志输出。 */
if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
if (fd > STDERR_FILENO) close(fd);
}
}
会决定是否要以守护进程方式运行。
本文作者:yowayimono
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!