【源碼】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);