- 前言
- 問題
- 階段 1:基本初始化
- 階段 2:檢查哨兵模式,執行 RDB 或 AOF 檢測
- 階段 3:運行參數解析
- 階段 4:初始化 server
- 階段 5:執行事件驅動框架
- 參考鏈接
- Redis 源碼簡潔剖析系列
前言
main 函數是 Redis 整個運行程序的入口。源碼主要在 server.c
文件中。
前面 6 篇文章分析了 Redis 的基礎數據結構。
- Redis 源碼簡潔剖析 01 - 環境配置
- Redis 源碼簡潔剖析 02 - SDS 字符串
- Redis 源碼簡潔剖析 03 - Dict Hash 基礎
- Redis 源碼簡潔剖析 04 - Sorted Set 有序集合
- Redis 源碼簡潔剖析 05 - ziplist 壓縮列表
- Redis 源碼簡潔剖析 06 - quicklist 和 listpack
問題
- Redis server 啓動後具體會做哪些初始化操作?
- Redis server 初始化時有哪些關鍵配置項?
- Redis server 如何開始處理客戶端請求?
階段 1:基本初始化
基本的初始化工作,包括設置 server 運行的時區等。
//設置時區
setlocale(LC_COLLATE,"");
tzset();
...
//設置隨機種子
char hashseed[16];
getRandomHexChars(hashseed,sizeof(hashseed));
dictSetHashFunctionSeed((uint8_t*)hashseed);
階段 2:檢查哨兵模式,執行 RDB 或 AOF 檢測
Redis Server 可能以哨兵模式
運行。哨兵模式需要額外的參數配置及初始化。
// 判斷 server 是否爲「哨兵模式」
if (server.sentinel_mode) {
// 初始化哨兵配置
initSentinelConfig();
// 初始化哨兵模式
initSentinel();
}
此外還會檢查是否要執行 RDB 檢測或 AOF 檢查,這對應了實際運行的程序是 redis-check-rdb 或 redis-check-aof。
// 運行的是 redis-check-rdb
if (strstr(argv[0],"redis-check-rdb") != NULL)
// 檢測 RDB 文件
redis_check_rdb_main(argc,argv,NULL);
// 運行的是 redis-check-aof
else if (strstr(argv[0],"redis-check-aof") != NULL)
// 檢測 AOF 文件
redis_check_aof_main(argc,argv);
階段 3:運行參數解析
main 函數會對命令行傳入的參數進行解析,並且調用 loadServerConfig 函數,對命令行參數和配置文件中的參數進行合併處理,然後爲 Redis 各功能模塊的關鍵參數設置合適的取值。
int main(int argc, char **argv) {
…
//保存命令行參數
for (j = 0; j < argc; j++) server.exec_argv[j] = zstrdup(argv[j]);
…
if (argc >= 2) {
…
//對每個運行時參數進行解析
while(j != argc) {
…
}
…
loadServerConfig(configfile,options);
}
loadServerConfig 函數是在 config.c 文件中實現的,該函數是以 Redis 配置文件和命令行參數的解析字符串爲參數,將配置文件中的所有配置項讀取出來,形成字符串。
階段 4:初始化 server
調用 initServer
函數,對 server 運行時的各種資源進行初始化工作。這主要包括:
- server 資源管理所需的數據結構初始化
- 鍵值對數據庫初始化
- server 網絡框架初始化
接着會再次判斷是否爲「哨兵模式」:
- 是哨兵模式,調用 sentinelIsRunning 函數,設置啓動哨兵模式
- 不是哨兵模式,調用 loadDataFromDisk 函數,從磁盤加載 AOF 或 RDB 文件,恢復之前的數據
// 初始化 server
initServer();
……
if (!server.sentinel_mode) {
……
InitServerLast();
// 從磁盤加載數據
loadDataFromDisk();
……
} else {
……
sentinelIsRunning();
……
}
資源管理
和 server 連接的客戶端、從庫等,Redis 用作緩存時的替換候選集,以及 server 運行時的狀態信息,這些資源的管理信息都會在 initServer 函數中進行初始化。
初始化數據庫
因爲一個 Redis 實例可以同時運行多個數據庫,所以 initServer 函數會使用一個循環,依次爲每個數據庫創建相應的數據結構。
for (j = 0; j < server.dbnum; j++) {
// 創建全局哈希表
server.db[j].dict = dictCreate(&dbDictType,NULL);
// 創建過期 key 的信息表
server.db[j].expires = dictCreate(&dbExpiresDictType,NULL);
server.db[j].expires_cursor = 0;
// 爲被 BLPOP 阻塞的 key 創建信息表
server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
// 爲將執行 PUSH 的阻塞 key 創建信息表
server.db[j].ready_keys = dictCreate(&objectKeyPointerValueDictType,NULL);
// 爲被 MULTI/WATCH 操作監聽的 key 創建信息表
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
……
}
創建事件驅動框架
針對每個監聽 IP 上可能發生的客戶端連接,都創建了監聽事件,用來監聽客戶端連接請求。同時,initServer 爲監聽事件設置了相應的處理函數 acceptTcpHandler。
這樣一來,只要有客戶端連接到 server 監聽的 IP 和端口,事件驅動框架就會檢測到有連接事件發生,然後調用 acceptTcpHandler 函數來處理具體的連接。
//創建事件循環框架
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);
}
…
階段 5:執行事件驅動框架
高效處理高併發的客戶端連接請求,Redis 採用了事件驅動框架,來併發處理不同客戶端的連接和讀寫請求。main 函數最後會調用 aeMain
函數進入事件驅動框架,循環處理各種觸發的事件。
// 事件驅動框架,循環處理各種觸發的事件
aeMain(server.el);
// 循環結束,刪除 eventLoop
aeDeleteEventLoop(server.el);
aeMain
函數核心調用了 aeProcessEvents
函數。aeProcessEvents 函數的具體源碼將在之後的文章中分析。
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
// 循環調用
while (!eventLoop->stop) {
// 核心函數,處理事件的邏輯
aeProcessEvents(eventLoop, AE_ALL_EVENTS|
AE_CALL_BEFORE_SLEEP|
AE_CALL_AFTER_SLEEP);
}
}
參考鏈接
Redis 源碼簡潔剖析系列
Java 編程思想-最全思維導圖-GitHub 下載鏈接,需要的小夥伴可以自取~
原創不易,希望大家轉載時請先聯繫我,並標註原文鏈接。