- 前言
- 問題
- 階段 1:基本初始化
- 階段 2:檢查哨兵模式,執(zhí)行 RDB 或 AOF 檢測
- 階段 3:運(yùn)行參數(shù)解析
- 階段 4:初始化 server
- 階段 5:執(zhí)行事件驅(qū)動(dòng)框架
- 參考鏈接
- Redis 源碼簡潔剖析系列
前言
main 函數(shù)是 Redis 整個(gè)運(yùn)行程序的入口。源碼主要在 server.c 文件中。
前面 6 篇文章分析了 Redis 的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。
- Redis 源碼簡潔剖析 01 - 環(huán)境配置
- Redis 源碼簡潔剖析 02 - SDS 字符串
- Redis 源碼簡潔剖析 03 - Dict Hash 基礎(chǔ)
- Redis 源碼簡潔剖析 04 - Sorted Set 有序集合
- Redis 源碼簡潔剖析 05 - ziplist 壓縮列表
- Redis 源碼簡潔剖析 06 - quicklist 和 listpack
問題
- 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);

階段 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 源碼簡潔剖析系列
Java 編程思想-最全思維導(dǎo)圖-GitHub 下載鏈接,需要的小伙伴可以自取~
原創(chuàng)不易,希望大家轉(zhuǎn)載時(shí)請(qǐng)先聯(lián)系我,并標(biāo)注原文鏈接。