On this page
article
Server 启动过程
本文是 Redis 6 启动过程源码解析,详细介绍了 redisServer 结构体、main 函数执行流程、initServer 函数初始化流程等内容。
如无特殊说明,本文涉及的源码均位于 server.c 与 server.h 中
Redis 服务器的启动命令格式如下:
redis-server [ configfile ] [ options ]
# eg: redis-server /path/to/redis.conf --port 12345 --protected-mode no
configfile
参数用于指定配置文件;options
参数用于指定启动配置项,用于覆盖配置文件中的配置。
redisServer 结构体
Redis 在 server.h 中定义了 redisServer
结构体,用于存储 Redis 服务器的信息,包括服务器配置项和运行时数据(例如网络连接信息、数据库、命令表、客户端信息、从服务器信息、统计信息等数据):
struct redisServer {
pid_t pid;
pthread_t main_thread_id;
char *configfile;
char *executable;
char **exec_argv;
int dynamic_hz;
int config_hz;
...
int failover_state;
};
并定义了一个全局变量 server
供应用使用:
extern struct redisServer server;
我们可以通过 INFO
命令获取服务器的信息,该命令主要返回以下信息:
server
:有关 Redis 服务器的常规信息;clients
:客户端连接信息;memory
:内存消耗相关信息;persistence
:RDB 与 AOF 持久化信息;stats
:常规统计信息;replication
:副本信息;cpu
:CPU 占用信息;commandstats
:Redis 命令统计信息;cluster
:Redis Cluster 集群信息;modules
:模块信息;keyspace
:数据库相关的统计信息;errorstats
:Redis 错误统计信息。
以上 INFO
命令信息中,除了 cpu 与 memory 信息外,其他的数据均是根据 server
变量计算来的。
main 函数
server.c 中的 main 函数负责 Redis 服务的启动,流程如下:
int main(int argc, char **argv) {
...
// [1] 是否启用 sentinel 模式
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] 配置 sentinel
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);
...
[1]
:检查 Redis Server 是否以 Sentinel 模式启动;[2]
:initServerConfig()
函数将redisServer
中记录的配置项的属性初始化为默认值、ACLInit()
函数将初始化 ACL 机制、moduleInitModulesSystem()
函数将初始化 Module 机制;[3]
:记录 Redis Server 可执行程序的路径以及启动参数,以便后续重启服务器;[4]
:如果是以 Sentinel 模式启动的,则在这里初始化 Sentinel 机制;[5]
:如果当前运行的是 redis-check-rdb 或者 redis-check-aof,则执行redis_check_xxx_main()
函数,尝试验证并修复 RDB 快照或 AOF 日志,修复完成后退出程序。
if (argc >= 2) {
j = 1; /* First option to parse in argv[] */
sds options = sdsempty();
// [6] 优先处理版本号获取
if (strcmp(argv[1], "-v") == 0 ||
strcmp(argv[1], "--version") == 0) version();
...
// [7] 设定配置文件路径
if (argv[1][0] != '-') {
server.configfile = getAbsolutePath(argv[1]);
zfree(server.exec_argv[1]);
server.exec_argv[1] = zstrdup(server.configfile);
j = 2;
}
// [8] 解析命令参数
while(j < argc) {
if (argv[j][0] == '-' && argv[j][1] == '\0' && (j == 1 || j == argc-1)) {
config_from_stdin = 1;
}
else if (argv[j][0] == '-' && argv[j][1] == '-') {
if (sdslen(options)) options = sdscat(options,"\n");
options = sdscat(options,argv[j]+2);
options = sdscat(options," ");
} else {
options = sdscatrepr(options,argv[j],strlen(argv[j]));
options = sdscat(options," ");
}
j++;
}
// [9] 加载命令配置的参数
loadServerConfig(server.configfile, config_from_stdin, options);
// [10] sentinel 配置信息
if (server.sentinel_mode) loadSentinelConfigFromQueue();
sdsfree(options);
}
if (server.sentinel_mode) sentinelCheckConfigFile();
[6]
:优先处理-v
、--version
、--help
、-h
、--test-memory
等命令;[7]
:如果命令中可执行文件后的第一个参数不以-
开始,说明是配置文件参数,将配置文件路径转化为绝对路径,并存入server.configfile
中;[8]
:读取命令中的配置参数,然后将他们追加到 sds 字符串options
中;[9]
:将从命令中拿到的所有配置项,存入server.configfile
中;[10]
:如果是以 Sentinel 模式启动的,加载 Sentinel 配置信息。
// [11] Redis 进程守护方式设定
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] 非 sentinel 模式下初始化一些组件
moduleInitModulesSystemLast();
moduleLoadFromQueue();
ACLLoadUsersAtStartup();
// 创建后台线程、I/O 线程
InitServerLast();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
serverLog(...);
exit(1);
}
}
...
} else {
ACLLoadUsersAtStartup();
InitServerLast();
// [15] 如果是 sentinel 模式,这里启动
sentinelIsRunning();
...
}
...
// [16] 绑定 CPU
redisSetCpuAffinity(server.server_cpulist);
setOOMScoreAdj(-1);
// [17] 启动 AE 事件处理器
aeMain(server.el);
// [18] 终止程序时,这里做收尾工作
aeDeleteEventLoop(server.el);
return 0;
}
[11]
:通过server.supervised
决定是否通过 systemd 服务启动 Redis,如果未设置server.supervised
,且设置了server.daemonize
,则以守护进程的方式启动 Redis;[12]
:打印启动信息;[13]
:initServer()
函数初始化 Redis 运行时数据、createPidFile()
函数创建 pid 文件;[14]
:如果是非 Sentinel 模式启动的:moduleInitModulesSystemLast()
初始化在前边工作均完成后才能加载的静态客户端,例如selectDb()
;moduleLoadFromQueue()
加载配置文件中指定的 Module 模块;ACLLoadUsersAtStartup()
加载 ACL 用户控制列表;InitServerLast()
创建后台线程、I/O 线程;loadDataFromDisk()
从磁盘加载 RDB 快照和 AOF 日志。
[15]
:如果是以 Sentinel 模式启动的,调用sentinelIsRunning()
启动 Sentinel 机制;[16]
:redisSetCpuAffinity()
尽力将 Redis 主线程绑定到server.server_cpulist
中配置的 CPU 上,减少不必要的线程切换;[17]
:启动事件循环器,事件循环器是 Redis 的核心;[18]
:跳出事件循环器说明 Redis 服务应当终止了,因此调用aeDeleteEventLoop()
清理事件,退出程序。
initServer 函数
在分析完整体 main
函数的启动流程后,我们重点关注下 Redis 运行时数据的初始化函数 initServer()
:
void initServer(void) {
int j;
// [1] 注册信号处理器
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
setupSignalHandlers();
// [2] 设置线程可被终止
makeThreadKillable();
// [3] 打开日志文件
if (server.syslog_enabled) {
openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
server.syslog_facility);
}
// [4] 开始初始化 server 变量属性
server.aof_state = server.aof_enabled ? AOF_ON : AOF_OFF;
server.hz = server.config_hz;
server.pid = getpid();
...
// [5] 创建常用字符集
createSharedObjects();
adjustOpenFilesLimit();
const char *clk_msg = monotonicInit();
serverLog(LL_NOTICE, "monotonic clock: %s", clk_msg);
// [6] 创建 AE 事件循环器
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
if (server.el == NULL) {
...
exit(1);
}
[1]
:注册信号处理程序,当 systemd 向 Redis 发送停止信号时,可以安全的释放资源;[2]
:设置线程随时响应 CANCEL 信号,以便停止程序;[3]
:当开启 log 时,则调用openlog()
打开日志文件;[4]
:初始化server
变量中的属性;[5]
:createSharedObjects()
函数会创建共享数据集,内容包含常用的字符串(例如+OK\r\n
等)以及小数字 0 ~ 9999、adjustOpenFilesLimit()
函数会提高系统允许打开的文件描述符上线,避免由于大量的客户端连接导致错误;[6]
:创建事件循环器。
server.db = zmalloc(sizeof(redisDb)*server.dbnum);
// [7] 启动网络端口
if (server.port != 0 &&
listenToPort(server.port,&server.ipfd) == C_ERR) {
...
exit(1);
}
if (server.tls_port != 0 &&
listenToPort(server.tls_port,&server.tlsfd) == C_ERR) {
...
exit(1);
}
if (server.unixsocket != NULL) {
...
}
...
// [8] 初始化内存数据
for (j = 0; j < server.dbnum; j++) {
server.db[j].dict = dictCreate(&dbDictType,NULL);
server.db[j].expires = dictCreate(&dbExpiresDictType,NULL);
...
}
// [9] 初始化 LRU/LRU 样本池
evictionPoolAlloc();
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
server.pubsub_patterns = dictCreate(&keylistDictType,NULL);
...
[7]
:根据配置完成 TCP Socket、TLS Socket、Unix Socket 的创建,如果以上三个配置都没设置则报错退出;[8]
:初始化主数据库server.db
;[9]
:初始化 LRU/LRU 样本池,用于实现 LRU/LFU 近似算法。
// [10] 创建时间事件处理器,第二个参数为 1,表示 1s 后触发
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
serverPanic("Can't create event loop timers.");
exit(1);
}
...
// [11] 向 AE 处理器中注册链接事件处理器等
if (createSocketAcceptHandler(&server.ipfd, acceptTcpHandler) != C_OK) {
serverPanic("Unrecoverable error creating TCP socket accept handler.");
}
if (createSocketAcceptHandler(&server.tlsfd, acceptTLSHandler) != C_OK) {
serverPanic("Unrecoverable error creating TLS socket accept handler.");
}
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
if (aeCreateFileEvent(server.el, server.module_blocked_pipe[0], AE_READABLE,
moduleBlockedClientPipeReadable,NULL) == AE_ERR) {
serverPanic(
"Error registering the readable event for the module "
"blocked clients subsystem.");
}
// [12] 注册钩子函数
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
// [13] 提前打开 AOF
if (server.aof_state == AOF_ON) {
server.aof_fd = open(server.aof_filename,
O_WRONLY|O_APPEND|O_CREAT,0644);
if (server.aof_fd == -1) {
serverLog(LL_WARNING, "Can't open the append-only file: %s",
strerror(errno));
exit(1);
}
}
// [14] 设定最大内存使用上限
if (server.arch_bits == 32 && server.maxmemory == 0) {
...
server.maxmemory = 3072LL*(1024*1024); /* 3 GB */
server.maxmemory_policy = MAXMEMORY_NO_EVICTION;
}
// [15] 如果启用 cluster,则初始化
if (server.cluster_enabled) clusterInit();
// [16] 初始化属性
replicationScriptCacheInit();
// [17] 初始化 LUA 机制
scriptingInit(1);
// [18] 初始化慢日志
slowlogInit();
// [19] 初始化延迟监控
latencyMonitorInit();
// [20] 初始化默认 ACL 密码
ACLUpdateDefaultUserPassword(server.requirepass);
}
[10]
:创建一个一秒后触发的 时间事件,执行函数为serverCron
,负责处理 Redis 中的定时任务,如清理过期数据、生成 RDB 文件等;[11]
:注册事件处理函数:- TCP 连接事件处理函数
acceptTcpHandler
; - TLS 握手事件处理函数
acceptTLSHandler
; - Unix Socket 可读事件处理函数
acceptUnixHandler
; - 阻塞管道可读事件处理函数
moduleBlockedClientPipeReadable
。
- TCP 连接事件处理函数
[12]
:注册事件循环器的钩子函数,beforeSleep
函数在事件处理器阻塞前调用,afterSleep
在事件处理器被唤醒时调用;[13]
:如果配置打开了 AOF,则预先打开 AOF 文件;[14]
:如果 Redis 运行在 32 位的机器上,则将内存的使用上限设置为 3G;[15]
:如果以 Cluster 模式启动,则调用clusterInit()
初始化 Cluster 机制;[16]
:初始化server.repl_scriptcache_dict
属性;[17]
:初始化 LUA 机制;[18]
:初始化慢日志;[19]
:初始化延迟监控机制;[20]
:初始化 ACL 默认密码(如果存在)。