Redis服务器负责接收处理用户请求,为用户提供服务。
Redis服务器的启动命令格式如下:
redis-server [ configfile ] [ options ]
configfile参数指定配置文件。options参数指定启动配置项,它可以覆盖配置文件中的配置项,如
redis-server /path/to/redis.conf --port 7777 --protected-mode no
该命令启动Redis服务,并指定了配置文件/path/to/redis.conf,给出了两个启动配置项:port、protected-mode。
本文通过阅读Redis源码,分析Redis启动过程,内容摘自新书《Redis核心原理与实践》。
本文涉及Redis的很多概念,如事件循环器、ACL、Module、LUA、慢日志等,这些功能在作者新书《Redis核心原理与实践》做了详尽分析,感兴趣的读者可以参考本书。
服务器定义
提示:本章代码如无特殊说明,均在server.h、server.c中。
Redis中定义了server.h/redisServer结构体,存储Redis服务器信息,包括服务器配置项和运行时数据(如网络连接信息、数据库redisDb、命令表、客户端信息、从服务器信息、统计信息等数据)。
struct redisServer {
pid_t pid;
pthread_t main_thread_id;
char *configfile;
char *executable;
char **exec_argv;
...
}
redisServer中的属性很多,这里不一一列举,等到分析具体功能时再说明相关的server属性。
server.h中定义了一个redisServer全局变量:
extern struct redisServer server;
本书说到的server变量,如无特殊说明,都是指该redisServer全局变量。例如,第1部分说过server.list_max_ziplist_size等属性,正是指该变量的属性。
可以使用INFO命令获取服务器的信息,该命令主要返回以下信息:
server:有关Redis服务器的常规信息。
clients:客户端连接信息。
memory:内存消耗相关信息。
persistence:RDB和AOF持久化信息。
stats:常规统计信息。
replication:主/副本复制信息。
cpu:CPU消耗信息。
commandstats:Redis 命令统计信息。
cluster:Redis Cluster集群信息。
modules:Modules模块信息。
keyspace:数据库相关的统计信息。
errorstats:Redis错误统计信息。
INFO命令响应内容中除了memory和cpu等统计数据,其他数据大部分都保存在redisServer中。
main函数
server.c/main函数负责启动Redis服务:
int main(int argc, char **argv) {
...
// [1]
server.sentinel_mode = checkForSentinelMode(argc,argv);
// [2]
initServerConfig();
ACLInit();
moduleInitModulesSystem();
tlsInit();
// [3]
server.executable = getAbsolutePath(argv[0]);
server.exec_argv = zmalloc(sizeof(char*)*(argc+1));
server.exec_argv[argc] = NULL;
for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);
// [4]
if (server.sentinel_mode) {
initSentinelConfig();
initSentinel();
}
// [5]
if (strstr(argv[0],"redis-check-rdb") != NULL)
redis_check_rdb_main(argc,argv,NULL);
else if (strstr(argv[0],"redis-check-aof") != NULL)
redis_check_aof_main(argc,argv);
// more
}
【1】检查该Redis服务器是否以sentinel模式启动。
【2】initServerConfig函数将redisServer中记录配置项的属性初始化为默认值。ACLInit函数初始化ACL机制,moduleInitModulesSystem函数初始化Module机制。
【3】记录Redis程序可执行路径及启动参数,以便后续重启服务器。
【4】如果以Sentinel模式启动,则初始化Sentinel机制。
【5】如果启动程序是redis-check-rdb或redis-check-aof,则执行redis_check_rdb_main或redis_check_aof_main函数,它们尝试检验并修复RDB、AOF文件后便退出程序。
Redis编译完成后,会生成5个可执行程序:
redis-server:Redis执行程序。
redis-sentinel:Redis Sentinel执行程序。
redis-cli:Redis客户端程序。
redis-benchmark:Redis性能压测工具。
redis-check-aof、redis-check-rdb:用于检验和修复RDB、AOF持久化文件的工具。
继续分析main函数:
int main(int argc, char **argv) {
...
if (argc >= 2) {
j = 1;
sds options = sdsempty();
char *configfile = NULL;
// [6]
if (strcmp(argv[1], "-v") == 0 ||
strcmp(argv[1], "--version") == 0) version();
...
// [7]
if (argv[j][0] != '-' || argv[j][1] != '-') {
configfile = argv[j];
server.configfile = getAbsolutePath(configfile);
zfree(server.exec_argv[j]);
server.exec_argv[j] = zstrdup(server.configfile);
j++;
}
// [8]
while(j != argc) {
...
}
// [9]
if (server.sentinel_mode && configfile && *configfile == '-') {
...
exit(1);
}
// [10]
resetServerSaveParams();
loadServerConfig(configfile,options);
sdsfree(options);
}
...
}
【6】对-v、--version、--help、-h、--test-memory等命令进行优先处理。
strcmp函数比较两个字符串str1、str2,若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数。
【7】如果启动命令的第二个参数不是以“--”开始的,则是配置文件参数,将配置文件路径转化为绝对路径,存入server.configfile中。
【8】读取启动命令中的启动配置项,并将它们拼接到一个字符串中。
【9】以Sentinel模式启动,必须指定配置文件,否则直接报错退出。
【10】config.c/resetServerSaveParams函数重置server.saveparams属性(该属性存放RDB SAVE配置)。config.c/loadServerConfig函数从配置文件中加载所有配置项,并使用启动命令配置项覆盖配置文件中的配置项。
提示:config.c中的configs数组定义了大多数配置选项与server属性的对应关系:
standardConfig configs[] = {
createBoolConfig("rdbchecksum", NULL, IMMUTABLE_CONFIG, server.rdb_checksum, 1, NULL, NULL),
createBoolConfig("daemonize", NULL, IMMUTABLE_CONFIG, server.daemonize, 0, NULL, NULL),
...
}
配置项rdbchecksum对应server.rdb_checksum属性,默认值为1(即bool值yes),其他配置项以此类推。如果读者需要查找配置项对应的server属性和默认值,则可以从中查找。
下面继续分析main函数:
int main(int argc, char **argv) {
...
// [11]
server.supervised = redisIsSupervised(server.supervised_mode);
int background = server.daemonize && !server.supervised;
if (background) daemonize();
// [12]
serverLog(LL_WARNING, "oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo");
...
// [13]
initServer();
if (background || server.pidfile) createPidFile();
...
if (!server.sentinel_mode) {
...
// [14]
moduleLoadFromQueue();
ACLLoadUsersAtStartup();
InitServerLast();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
...
exit(1);
}
}
...
} else {
// [15]
InitServerLast();
sentinelIsRunning();
...
}
...
// [16]
redisSetCpuAffinity(server.server_cpulist);
setOOMScoreAdj(-1);
// [17]
aeMain(server.el);
// [18]
aeDeleteEventLoop(server.el);
return 0;
}
【11】server.supervised属性指定是否以upstart服务或systemd服务启动Redis。如果配置了server.daemonize且没有配置server.supervised,则以守护进程的方式启动Redis。
【12】打印启动日志。
【13】initServer函数初始化Redis运行时数据,createPidFile函数创建pid文件。
【14】如果非Sentinel模式启动,则完成以下操作:
(1)moduleLoadFromQueue函数加载配置文件指定的Module模块;
(2)ACLLoadUsersAtStartup函数加载ACL用户控制列表;
(3)InitServerLast函数负责创建后台线程、I/O线程,该步骤需在Module模块加载后再执行;
(4)loadDataFromDisk函数从磁盘中加载AOF或RDB文件。
(5)如果以Cluster模式启动,那么还需要验证加载的数据是否正确。
【15】如果以Sentinel模式启动,则调用sentinelIsRunning函数启动Sentinel机制。
【16】尽可能将Redis主线程绑定到server.server_cpulist配置的CPU列表上,Redis 4开始使用多线程,该操作可以减少不必要的线程切换,提高性能。
【17】启动事件循环器。事件循环器是Redis中的重要组件。在Redis运行期间,由事件循环器提供服务。
【18】执行到这里,说明Redis服务已停止,aeDeleteEventLoop函数清除事件循环器中的事件,最后退出程序。
参考资料:
小海鲸 http://www.xiao-haijing.com