如无特殊说明,本文涉及的源码均位于 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
  • [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 默认密码(如果存在)。