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!