Redis 源碼簡潔剖析 07 - main 函數(shù)啟動(dòng)

前言

main 函數(shù)是 Redis 整個(gè)運(yùn)行程序的入口。源碼主要在 server.c 文件中。

前面 6 篇文章分析了 Redis 的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。

問題

  • Redis server 啟動(dòng)后具體會(huì)做哪些初始化操作?
  • Redis server 初始化時(shí)有哪些關(guān)鍵配置項(xiàng)?
  • Redis server 如何開始處理客戶端請(qǐng)求?

階段 1:基本初始化

基本的初始化工作,包括設(shè)置 server 運(yùn)行的時(shí)區(qū)等。

//設(shè)置時(shí)區(qū)
setlocale(LC_COLLATE,"");
tzset();
...
//設(shè)置隨機(jī)種子
char hashseed[16];
getRandomHexChars(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed);
image

階段 2:檢查哨兵模式,執(zhí)行 RDB 或 AOF 檢測

Redis Server 可能以哨兵模式運(yùn)行。哨兵模式需要額外的參數(shù)配置及初始化。

// 判斷 server 是否為「哨兵模式」
if (server.sentinel_mode) {
    // 初始化哨兵配置
    initSentinelConfig();
    // 初始化哨兵模式
    initSentinel();
}

此外還會(huì)檢查是否要執(zhí)行 RDB 檢測或 AOF 檢查,這對(duì)應(yīng)了實(shí)際運(yùn)行的程序是 redis-check-rdb 或 redis-check-aof。

// 運(yùn)行的是 redis-check-rdb
if (strstr(argv[0],"redis-check-rdb") != NULL)
    // 檢測 RDB 文件
    redis_check_rdb_main(argc,argv,NULL);
    // 運(yùn)行的是 redis-check-aof
else if (strstr(argv[0],"redis-check-aof") != NULL)
    // 檢測 AOF 文件
    redis_check_aof_main(argc,argv);

階段 3:運(yùn)行參數(shù)解析

main 函數(shù)會(huì)對(duì)命令行傳入的參數(shù)進(jìn)行解析,并且調(diào)用 loadServerConfig 函數(shù),對(duì)命令行參數(shù)和配置文件中的參數(shù)進(jìn)行合并處理,然后為 Redis 各功能模塊的關(guān)鍵參數(shù)設(shè)置合適的取值。

int main(int argc, char **argv) {
    …
    //保存命令行參數(shù)
    for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);
    …
    if (argc >= 2) {
    …
        //對(duì)每個(gè)運(yùn)行時(shí)參數(shù)進(jìn)行解析
        while(j != argc) {
            …
        }
    …
    loadServerConfig(configfile,options);
}

loadServerConfig 函數(shù)是在 config.c 文件中實(shí)現(xiàn)的,該函數(shù)是以 Redis 配置文件和命令行參數(shù)的解析字符串為參數(shù),將配置文件中的所有配置項(xiàng)讀取出來,形成字符串。

階段 4:初始化 server

調(diào)用 initServer 函數(shù),對(duì) server 運(yùn)行時(shí)的各種資源進(jìn)行初始化工作。這主要包括:

  • server 資源管理所需的數(shù)據(jù)結(jié)構(gòu)初始化
  • 鍵值對(duì)數(shù)據(jù)庫初始化
  • server 網(wǎng)絡(luò)框架初始化

接著會(huì)再次判斷是否為「哨兵模式」:

  • 是哨兵模式,調(diào)用 sentinelIsRunning 函數(shù),設(shè)置啟動(dòng)哨兵模式
  • 不是哨兵模式,調(diào)用 loadDataFromDisk 函數(shù),從磁盤加載 AOF 或 RDB 文件,恢復(fù)之前的數(shù)據(jù)
// 初始化 server
initServer();
……

if (!server.sentinel_mode) {
    ……
    InitServerLast();
    // 從磁盤加載數(shù)據(jù)
    loadDataFromDisk();
    ……
} else {
    ……
    sentinelIsRunning();
    ……
}

資源管理

和 server 連接的客戶端、從庫等,Redis 用作緩存時(shí)的替換候選集,以及 server 運(yùn)行時(shí)的狀態(tài)信息,這些資源的管理信息都會(huì)在 initServer 函數(shù)中進(jìn)行初始化。

初始化數(shù)據(jù)庫

因?yàn)橐粋€(gè) Redis 實(shí)例可以同時(shí)運(yùn)行多個(gè)數(shù)據(jù)庫,所以 initServer 函數(shù)會(huì)使用一個(gè)循環(huán),依次為每個(gè)數(shù)據(jù)庫創(chuàng)建相應(yīng)的數(shù)據(jù)結(jié)構(gòu)。

for (j = 0; j < server.dbnum; j++) {
    // 創(chuàng)建全局哈希表
    server.db[j].dict = dictCreate(&dbDictType,NULL);
    // 創(chuàng)建過期 key 的信息表
    server.db[j].expires = dictCreate(&dbExpiresDictType,NULL);
    server.db[j].expires_cursor = 0;
    // 為被 BLPOP 阻塞的 key 創(chuàng)建信息表
    server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
    // 為將執(zhí)行 PUSH 的阻塞 key 創(chuàng)建信息表
    server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
    // 為被 MULTI/WATCH 操作監(jiān)聽的 key 創(chuàng)建信息表
    server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
    ……
}

創(chuàng)建事件驅(qū)動(dòng)框架

針對(duì)每個(gè)監(jiān)聽 IP 上可能發(fā)生的客戶端連接,都創(chuàng)建了監(jiān)聽事件,用來監(jiān)聽客戶端連接請(qǐng)求。同時(shí),initServer 為監(jiān)聽事件設(shè)置了相應(yīng)的處理函數(shù) acceptTcpHandler。

這樣一來,只要有客戶端連接到 server 監(jiān)聽的 IP 和端口,事件驅(qū)動(dòng)框架就會(huì)檢測到有連接事件發(fā)生,然后調(diào)用 acceptTcpHandler 函數(shù)來處理具體的連接。

//創(chuàng)建事件循環(huán)框架
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
…
//開始監(jiān)聽設(shè)置的網(wǎng)絡(luò)端口
if (server.port != 0 &&
        listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
        exit(1);
…
//為 server 后臺(tái)任務(wù)創(chuàng)建定時(shí)事件
if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create event loop timers.");
        exit(1);
}
…

階段 5:執(zhí)行事件驅(qū)動(dòng)框架

高效處理高并發(fā)的客戶端連接請(qǐng)求,Redis 采用了事件驅(qū)動(dòng)框架,來并發(fā)處理不同客戶端的連接和讀寫請(qǐng)求。main 函數(shù)最后會(huì)調(diào)用 aeMain 函數(shù)進(jìn)入事件驅(qū)動(dòng)框架,循環(huán)處理各種觸發(fā)的事件。

// 事件驅(qū)動(dòng)框架,循環(huán)處理各種觸發(fā)的事件
aeMain(server.el);
// 循環(huán)結(jié)束,刪除 eventLoop
aeDeleteEventLoop(server.el);

aeMain 函數(shù)核心調(diào)用了 aeProcessEvents 函數(shù)。aeProcessEvents 函數(shù)的具體源碼將在之后的文章中分析。

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    // 循環(huán)調(diào)用
    while (!eventLoop->stop) {
        // 核心函數(shù),處理事件的邏輯
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|
                                   AE_CALL_BEFORE_SLEEP|
                                   AE_CALL_AFTER_SLEEP);
    }
}

參考鏈接

Redis 源碼簡潔剖析系列

最簡潔的 Redis 源碼剖析系列文章

Java 編程思想-最全思維導(dǎo)圖-GitHub 下載鏈接,需要的小伙伴可以自取~

原創(chuàng)不易,希望大家轉(zhuǎn)載時(shí)請(qǐng)先聯(lián)系我,并標(biāo)注原文鏈接。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容