【原始碼】Redis Server啟動過程
阿新 • • 發佈:2022-02-07
本文基於社群版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);