* is parsed, if any. */
while(j != argc) {
if (argv[j][0] == '-' && argv[j][1] == '-') {
/* Option name */
if (!strcmp(argv[j], "--check-rdb")) {
/* Argument has no options, need to skip for parsing. */
j++;
continue;
}
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
} else {
/* Option argument */
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
}
j++;
}
if (server.sentinel_mode && configfile && *configfile == '-') {
serverLog(LL_WARNING,
"Sentinel config from STDIN not allowed.");
serverLog(LL_WARNING,
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
}
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
}
serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");
serverLog(LL_WARNING,
"Redis version=%s, bits=%d, commit=%s, modified=%d, pid=%d, just started",
REDIS_VERSION,
(sizeof(long) == 8) ? 64 : 32,
redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0,
(int)getpid());
if (argc == 1) {
serverLog(LL_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis");
} else {
serverLog(LL_WARNING, "Configuration loaded");
}
server.supervised = redisIsSupervised(server.supervised_mode);
int background = server.daemonize && !server.supervised;
if (background) daemonize();
initServer();
if (background || server.pidfile) createPidFile();
redisSetProcTitle(argv[0]);
redisAsciiArt();
checkTcpBacklogSettings();
if (!server.sentinel_mode) {
/* Things not needed when running in Sentinel mode. */
serverLog(LL_WARNING,"Server initialized");
#ifdef __linux__
linuxMemoryWarnings();
#endif
moduleLoadFromQueue();
InitServerLast();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
serverLog(LL_WARNING,
"You can't have keys in a DB different than DB 0 when in "
"Cluster mode. Exiting.");
exit(1);
}
}
if (server.ipfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
} else {
InitServerLast();
sentinelIsRunning();
}
/* Warning the user about suspicious maxmemory setting. */
if (server.maxmemory > 0 && server.maxmemory < 1024*1024) {
serverLog(LL_WARNING,"WARNING: You specified a maxmemory value that is less than 1MB (current value is %llu bytes). Are you sure this is what you really want?", server.maxmemory);
}
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;
}
main函数中执行部分包含下面几个阶段:
1. 基本初始化阶段
2. 检查配置阶段
3. 运行参数解析阶段
4. 初始化server阶段
5. 执行事件驱动框架阶段
### 基本初始化阶段
main 函数开始有一段宏定义覆盖的代码,这部分如何定义了REDIS\_TEST宏定义,并且Redis server启动时的参数符合测试参数,那么main函数会执行响应的测试程序,比如启动参数argv的第二个参数等于test,且argv的第三个参数等于ziplist那么就执行ziplistTest测试函数。
#ifdef REDIS_TEST
if (argc == 3 && !strcasecmp(argv[1], “test”)) {
if (!strcasecmp(argv[2], “ziplist”)) {
return ziplistTest(argc, argv);
} else if (!strcasecmp(argv[2], “quicklist”)) {
quicklistTest(argc, argv);
} else if (!strcasecmp(argv[2], “intset”)) {
return intsetTest(argc, argv);
} else if (!strcasecmp(argv[2], “zipmap”)) {
return zipmapTest(argc, argv);
} else if (!strcasecmp(argv[2], “sha1test”)) {
return sha1Test(argc, argv);
} else if (!strcasecmp(argv[2], “util”)) {
return utilTest(argc, argv);
} else if (!strcasecmp(argv[2], “endianconv”)) {
return endianconvTest(argc, argv);
} else if (!strcasecmp(argv[2], “crc64”)) {
return crc64Test(argc, argv);
} else if (!strcasecmp(argv[2], “zmalloc”)) {
return zmalloc_test(argc, argv);
}
return -1; /* test not found */
}
#endif
后面主要设置server运行的地域、时区信息,设置内存分配异常处理句柄,设置哈希函数的随机种子等,该部分代码如下,
// 设置地域信息
setlocale(LC_COLLATE,"");
// 设置时区
tzset(); /* Populates 'timezone' global. */
// 设置内存分配异常处理句柄
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
// 生成随机数种子
srand(time(NULL)^getpid());
// 获取当前时间
gettimeofday(&tv,NULL);
// 设置随机种子
char hashseed[16];
// 设置哈希因子
getRandomHexChars(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed);
### 检查配置阶段
该阶段检查哨兵模式,并检查是否要执行RDB检测或AOF检测。Redis server启动后需要根据Redis配置的参数检查是否设置了哨兵模式,如果有则会调用initSentinelConfig函数,对哨兵模式的参数进行初始化设置,以及调用initSentinel函数,代码如下,
// 判断server是否设置为哨兵模式,如果是则进行哨兵模式初始化
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
接着检查是否要执行RDB检测或AOF检测,如果运行的是redis-check-rdb程序,调用redis\_check\_rdb\_main函数检测RDB文件,如果运行的是redis-check-aof程序,调用redis\_check\_aof\_main函数检测AOF文件,对应代码如下,
// 如果运行的是redis-check-rdb程序,调用redis_check_rdb_main函数检测RDB文件
if (strstr(argv[0],"redis-check-rdb") != NULL)
redis_check_rdb_main(argc,argv,NULL);
// 如果运行的是redis-check-aof程序,调用redis_check_aof_main函数检测AOF文件
else if (strstr(argv[0],"redis-check-aof") != NULL)
redis_check_aof_main(argc,argv);
... ...
}
### 运行参数解析阶段
该阶段解析运行参数,根据运行参数检查内存大小,获取绝对路径,并且调用loadServerConfig函数,并对命令行参数和配置文件中的参数进行设置,然后为Redis各功能模块的关键参数设置合适的值,以便Redis高效运行。
if (argc >= 2) {
j = 1; /* First option to parse in argv[] */
sds options = sdsempty();
char *configfile = NULL;
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 (argv[j][0] != '-' || argv[j][1] != '-') {
configfile = argv[j];
server.configfile = getAbsolutePath(configfile);
/* Replace the config file in server.exec_argv with
* its absolute path. */
zfree(server.exec_argv[j]);
server.exec_argv[j] = zstrdup(server.configfile);
j++;
}
while(j != argc) {
if (argv[j][0] == '-' && argv[j][1] == '-') {
/* Option name */
if (!strcmp(argv[j], "--check-rdb")) {
/* Argument has no options, need to skip for parsing. */
j++;
continue;
}
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
} else {
/* Option argument */
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
}
j++;
}
if (server.sentinel_mode && configfile && *configfile == '-') {
serverLog(LL_WARNING,
"Sentinel config from STDIN not allowed.");
serverLog(LL_WARNING,
"Sentinel needs config file on disk to save state. Exiting...");
exit(1);
}
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
}
### 初始化server阶段
完成参数解析和设置后,会打印服务器的一些日志信息,后面再调用initServer函数,对server运行时的各种资源进行初始化工作。主要包括server资源管理所需的数据结构初始化、键值对数据库初始化、server网络框架初始化等。而在调用完initServer函数后,main函数还会判断server是否为哨兵模式。如果是则会设置启动哨兵模式,否则则调用loadDataFromDisk函数,从磁盘上加载AOF或RDB文件,以便恢复之前的数据。
initServer();
if (background || server.pidfile) createPidFile();
redisSetProcTitle(argv[0]);
redisAsciiArt();
checkTcpBacklogSettings();
if (!server.sentinel_mode) {
/* Things not needed when running in Sentinel mode. */
serverLog(LL_WARNING,"Server initialized");
#ifdef __linux__
linuxMemoryWarnings();
#endif
moduleLoadFromQueue();
InitServerLast();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
serverLog(LL_WARNING,
"You can't have keys in a DB different than DB 0 when in "
"Cluster mode. Exiting.");
exit(1);
}
}
if (server.ipfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
} else {
InitServerLast();
sentinelIsRunning();
}
### 执行事件驱动框架阶段
为了能高效处理高并发的客户端连接请求,Redis采用了事件驱动框架来处理不同客户端的连接和读写请求。main 函数最后会调用aeMain函数进行事件驱动框架,开始循环处理各种触发的事件。代码片段如下,
int main(int argc, char **argv) {
… …
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
aeMain(server.el);
aeDeleteEventLoop(server.el);
return 0;
}