【源码】Redis Server启动过程

本文基于社区版Redis 4.0.8
 
 
 

1、 初始化参数配置

由函数initServerConfig()实现,具体操作就是给配置参数赋初始化值:
//设置时区
setlocale(LC_COLLATE,"");
//设置随机种子
char hashseed[16];
getRandomHexChars(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed);
//初始化module
void initServerConfig(void) {
    //serverCron函数执行频率,默认10ms
    server.hz = CONFIG_DEFAULT_HZ;
    //监听端口,默认6379
    server.port = CONFIG_DEFAULT_SERVER_PORT;
    server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
    server.dbnum = CONFIG_DEFAULT_DBNUM;
    ......
    //初始化命令表
    server.commands = dictCreate(&commandTableDictType,NULL);
    server.orig_commands = dictCreate(&commandTableDictType,NULL);
    populateCommandTable();
    server.delCommand = lookupCommandByCString("del");
    server.multiCommand = lookupCommandByCString("multi");
    server.lpushCommand = lookupCommandByCString("lpush");
    server.lpopCommand = lookupCommandByCString("lpop");
    server.rpopCommand = lookupCommandByCString("rpop");
    server.sremCommand = lookupCommandByCString("srem");
    server.execCommand = lookupCommandByCString("exec");
    server.expireCommand = lookupCommandByCString("expire");
    server.pexpireCommand = lookupCommandByCString("pexpire");
    ......
}
 

2、 加载并解析配置文件

在这一阶段,会对命令行传入的参数进行解析,并且调用 loadServerConfig 函数,对命令行参数和配置文件中的参数进行合并处理,然后为 Redis 各功能模块的关键参数设置合适的取值,以便 server 能高效地运行。
//filename表示配置文件全路径名称;
//options表示命令行输入的配置参数,如port=4000
void loadServerConfig(char *filename, char *options) {
    sds config = sdsempty();
    char buf[CONFIG_MAX_LINE+1];

    /* 加载配置文件到内存 */
    if (filename) {
        FILE *fp;

        if (filename[0] == '-' && filename[1] == '\0') {
            fp = stdin;
        } else {
            if ((fp = fopen(filename,"r")) == NULL) {
                serverLog(LL_WARNING,
                    "Fatal error, can't open config file '%s'", filename);
                exit(1);
            }
        }
        while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
            config = sdscat(config,buf);
        if (fp != stdin) fclose(fp);
    }
    /* Append the additional options */
    if (options) {
        config = sdscat(config,"\n");
        config = sdscat(config,options);
    }
    //解析配置
    loadServerConfigFromString(config);
    sdsfree(config);
}

3、 初始化服务器内部变量

在完成对运行参数的解析和设置后,main 函数会调用 initServer 函数,对 server 运行时的各种资源进行初始化工作。包括了 server 资源管理所需的数据结构初始化、键值对数据库初始化、server 网络框架初始化等。
最后调用 loadDataFromDisk 函数,从磁盘上加载 AOF 或者是 RDB 文件,以便恢复之前的数据。
void initServer(void) {
    /* 初始化需要的各种资源 */
    server.clients = listCreate();//初始化客户端链表
    server.pid = getpid();
    server.current_client = NULL;
    server.clients = listCreate();
    server.clients_to_close = listCreate();
    server.slaves = listCreate();
    server.monitors = listCreate();
   server.clients_pending_write = listCreate();
   server.slaveseldb = -1; /* Force to emit the first SELECT command. */
   server.unblocked_clients = listCreate();
   server.ready_keys = listCreate();
   server.clients_waiting_acks = listCreate();
   server.get_ack_from_slaves = 0;
   server.clients_paused = 0;
   server.system_memory_size = zmalloc_get_memory_size();

   createSharedObjects();
    //调用aeCreateEventLoop函数创建aeEventLoop结构体,并赋值给server结构的el变量
    //maxclients 变量的值大小,可以在 Redis 的配置文件 redis.conf 中进行定义,默认值是 1000
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    if (server.el == NULL) {
        serverLog(LL_WARNING,
            "Failed creating the event loop. Error message: '%s'",
            strerror(errno));
        exit(1);
    }
    ......
    
    /* 创建数据库结构*/
    for (j = 0; j < server.dbnum; j++) {
        server.db[j].dict = dictCreate(&dbDictType,NULL);
        server.db[j].expires = dictCreate(&keyptrDictType,NULL);
        server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
        server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
        server.db[j].id = j;
        server.db[j].avg_ttl = 0;
    } 
    
    ......
        
    //创建事件循环框架
    server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
    …
    //开始监听设置的网络端口
    if (server.port != 0 &&
            listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
            exit(1);
    …
    //为server后台任务创建定时事件
    if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
            serverPanic("Can't create event loop timers.");
            exit(1);
    }
    …
    //为每一个监听的IP设置连接事件的处理函数acceptTcpHandler
    for (j = 0; j < server.ipfd_count; j++) {
            if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
                acceptTcpHandler,NULL) == AE_ERR)
           { … }
    }
}

4、执行事件驱动框架

事件驱动框架是 Redis server 运行的核心。该框架一旦启动后,就会一直循环执行,每次循环会处理一批触发的网络读写事件。main 函数直接调用事件框架的主体函数 aeMain(在ae.c文件中)后,就进入事件处理循环了。当然,在进入事件驱动循环前,main 函数会分别调用 aeSetBeforeSleepProc 和 aeSetAfterSleepProc 两个函数,来设置每次进入事件循环前 server 需要执行的操作,以及每次事件循环结束后 server 需要执行的操作。下面代码显示了这部分的执行逻辑,你可以看下。
 
aeSetBeforeSleepProc(server.el,beforeSleep);
aeSetAfterSleepProc(server.el,afterSleep);
aeMain(server.el);
aeDeleteEventLoop(server.el);
 
 
 
 

posted @ 2022-02-07 20:22  茁壮的小草  阅读(314)  评论(0编辑  收藏  举报