redis源碼筆記 - initServer


initServer是redis對server進行初始化的入口,其由main調用,位於initServerConfig、命令行參數解析、守護進程判定之后,是server最重要的入口點。

盡管代碼看似簡單(102行代碼,且大量的賦值語句),但順藤摸瓜,有很多點值得仔細看看。接下來逐行分析:

 

函數第一件事是對信號進行處理:

 899     signal(SIGHUP, SIG_IGN);
 900     signal(SIGPIPE, SIG_IGN);
 901     setupSignalHandlers();

redis多作為守護進程運行,這時其不會有控制終端,首先忽略掉SIGHUP信號。(見APUE2 237頁);

SIGPIPE信號是在寫管道發現讀進程終止時產生的信號,寫已終止的SOCK_STREAM套接字同樣會產生此信號。redis作為server,不可避免的會遇到各種各樣的client,client意外終止導致產生的信號也應該在server啟動后忽略掉;

setupSignalHandlers函數處理的信號分兩類:

1)SIGTERM

  SIGTERM是kill命令發送的系統默認終止信號。也就是我們在試圖結束server時會觸發的信號。對這類信號,redis並沒有立即終止進程,其處理行為是,設置一個server.shutdown_asap,然后在下一次執行serverCron時,調用prepareForShutdown做清理工作,然后再退出程序。這樣可以有效的避免盲目的kill程序導致數據丟失,使得server可以優雅的退出。

2)SIGSEGV、SIGBUS、SIGFPE、SIGILL

      上述信號分別為無效內存引用(即我們常說的段錯誤),實現定義的硬件故障,算術運算錯誤(如除0)以及執行非法硬件指令。這類是非常嚴重的錯誤,redis的處理是通過sigsegvHandler,記錄出錯時的現場、執行必要的清理工作,然后kill自身。

除上面提到的7個信號意外,redis不再處理任何其他信號,均保留默認操作。

 

接下來,initServer通過四行代碼設置日志設施,如下:

 903     if (server.syslog_enabled) {
 904         openlog(server.syslog_ident, LOG_PID | LOG_NDELAY | LOG_NOWAIT,
 905             server.syslog_facility);
 906     }

記錄自己的線程ID:

 908     server.mainthread = pthread_self();

然后將當前處理的client(current_client)設置為NULL,將clients、slaves、monitors、unblocked_clients通通初始化為空的list。

接下來,調用createSharedObjects(),完成共同object的初始化。這里解釋下這個函數。redis在初始化時會把后續server執行過程中普遍需要的對象構造出來,如對執行成功的反饋值“+OK”,特定類型的錯誤值“+-ERR no such key\r\n”等等,這些對象多用在與客戶端的響應的純文本協議之中,現在版本共有30+,避免了臨時申請對象的開銷,同時也簡化了資源管理。

在執行此函數后,將會初始化事件循環server.el以及維護db所需要的數據結構,代碼如下:

 915     server.el = aeCreateEventLoop();
 916     server.db = zmalloc(sizeof(redisDb)*server.dbnum);

aeCreateEventLoop函數已經在介紹redis事件框架ae.c時提到了(http://www.cnblogs.com/liuhao/archive/2012/05/15/2502322.html),這里不再贅述。

接下來,初始化監聽的連接,包括SOCK_STREAM和UNIX_STREAM,如果創建失敗,或是均未設置,則退出程序的執行流程。

918     if (server.port != 0) {
 919         server.ipfd = anetTcpServer(server.neterr,server.port,server.bindaddr);
 920         if (server.ipfd == ANET_ERR) {
 921             redisLog(REDIS_WARNING, "Opening port %d: %s",
 922                 server.port, server.neterr);
 923             exit(1);
 924         }
 925     }
 926     if (server.unixsocket != NULL) {
 927         unlink(server.unixsocket); /* don't care if this fails */
 928         server.sofd = anetUnixServer(server.neterr,server.unixsocket,server.unixsocketper     m);
 929         if (server.sofd == ANET_ERR) {
 930             redisLog(REDIS_WARNING, "Opening socket: %s", server.neterr);
 931             exit(1);
 932         }
 933     }
 934     if (server.ipfd < 0 && server.sofd < 0) {
 935         redisLog(REDIS_WARNING, "Configured to not listen anywhere, exiting.");
 936         exit(1);
 937     }

接下來,程序初始化server的db數據結構,如下:

 938     for (j = 0; j < server.dbnum; j++) {
 939         server.db[j].dict = dictCreate(&dbDictType,NULL);
 940         server.db[j].expires = dictCreate(&keyptrDictType,NULL);
 941         server.db[j].blocking_keys = dictCreate(&keylistDictType,NULL);
 942         server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
 943         if (server.vm_enabled)
 944             server.db[j].io_keys = dictCreate(&keylistDictType,NULL);
 945         server.db[j].id = j;
 946     }

這里,對db數據結構內的各個dict類型加以說明。

db.dict的類型是dbDictType,它是數據庫所有數據的總的存儲和索引,存的是string->redisObject的一個映射,比如簡單的key-value,那么redisObject就是一個string,存儲鏈表結構,redisObject保存的就是鏈表。

db.expires的類型是keyptrDictType,它存儲的是設置了超時的key和對應的超時時間,即string->time_t的一個映射,這在介紹redis對過期值的處理時有所介紹(http://www.cnblogs.com/liuhao/archive/2012/05/25/2518185.html)。

db.blocking_keys和db.watched_keys均是keylistDictType類型,對應的value是list類型,key是redisObject。其value鏈表中存的是一系列client,表示特定redisObject狀態有變化時(如執行BLPOP,隊列中有新的元素即為狀態有變化)通知list中的所有客戶端。

因為新版中vm已經徹底廢棄,所以和vm相關聯的代碼都略過不表。

 

在對db的數據結構進行初始化后,對pubsub_channels進行了初始化,pubsub_channels同樣是keylistDictType的dict,用來記錄訂閱的所有client。

然后對pubsub_patterns進行了初始化。(這里插一句,redis的pubsub是個極其簡陋的實現,對持久化、網絡瞬斷均無處理,不推薦在項目中使用)

然后將兩個后台save子進程(bgsavechildpid和bgrewritechildpid)的pid初始化為-1,將用於aof和rewrite的buf初始化為empty的字符串,然后初始化了一系列的統計信息,略去不表。

有兩點需要解釋下:

 957     server.dirty = 0;

用來后續計算server維護的數據是否有更新,如果有,需要記錄aof和通知replication.

 967     server.unixtime = time(NULL);

用於時間值保留,其精度為s,類似於一個緩存。redis的代碼中有很多需要時間值的地方,只要其精度要求不是很高,server.unixtime又有合理的機制進行更新,就可以避免在每次需要時間值的時候執行昂貴的time系統調用。

 

接下來,注冊serverCron函數,這是個定期執行的函數,執行周期是100ms,這個函數也是個重點,以后會專門介紹。這里注冊是在1ms后調度serverCron,但:-),這里其實運行起來並不要求(保證)1ms后serverCron一定被調用,aeCreateTimeEvent只是注冊函數,真正何時執行取決於initServer執行后aeMain函數的執行,該函數觸發事件循環真正轉起來。

 968     aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL);

然后,initServer將監聽的描述符(ipfd - TCP  or sofd - UNIX_STREAM)加入事件監控列表,這里以ipfd舉例:

 969     if (server.ipfd > 0 && aeCreateFileEvent(server.el,server.ipfd,AE_READABLE,
 970         acceptTcpHandler,NULL) == AE_ERR) oom("creating file event");

在有連接請求進來后,acceptTcpHandler將會被調用,該函數調用accept接收連接,然后用accept函數返回的文件描述符創建一個client樁(一個redisClient對象),在server端代表連接進來的真正client。在創建client樁的時候,會將返回的這個描述符同樣添加進事件監控列表,監控READABLE事件,事件發生代表着客戶端發送數據過來,此時調用readQueryFromClient接收客戶端的query。

 

在創建上述監聽時間后,如果server設置了aof模式做持久化,將會打開對應的文件,保存相關的描述符,代碼如下:

 974     if (server.appendonly) {
 975         server.appendfd = open(server.appendfilename,O_WRONLY|O_APPEND|O_CREAT,0644);
 976         if (server.appendfd == -1) {
 977             redisLog(REDIS_WARNING, "Can't open the append-only file: %s",
 978                 strerror(errno));
 979             exit(1);
 980         }
 981     }

 

接下來,對於32位架構的系統,如果沒有設置最大內存占用限制(maxmemory),則將此限制設定為3.5G,並把maxmemory_policy設置為REDIS_MAXMEMORY_NO_EVICTION,表示在程序達到最大內存限制后,拒絕后續會增大內存使用的客戶端執行的命令。不過redis作為一個內存大殺器,3.5G、32位系統實在已經無法滿足日益增長的需求了。

 

函數執行最后,初始化slowlog,bio和一個隨機數種子。

slowlogInit()參見http://www.cnblogs.com/liuhao/archive/2012/05/20/2510725.html

bioInit()參見http://www.cnblogs.com/liuhao/archive/2012/05/17/2506810.html

 

旅程到此為止,over!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM