之前梳理過redis main函數主體流程
大體是 initServerConfig() -> loadServerConfig()
-> daemonize() -> initServer() -> aeSetBeforeSleepProc()
->aeMain() -> aeDeleteEventLoop();
initServerConfig() 初始化server的配置
loadServerConfig()會從配置文件里加載對應的配置
daemonize()創建守護進程
看一下daemonize的函數組成
void daemonize(void) {
int fd;
if (fork() != 0) exit(0); /* parent exits */
//設置為首進程
//之前parent和child運行在同一個session里,
//parent是會話(session)的領頭進程,
//parent進程作為會話的領頭進程,如果exit結束執行的話,
//那么子進程會成為孤兒進程,並被init收養。
//執行setsid()之后,child將重新獲得一個新的會話(session)id。
setsid(); /* create a new session */
/* Every output goes to /dev/null. If Redis is daemonized but
* the 'logfile' is set to 'stdout' in the configuration file
* it will not log at all. */
// /dev/null相當於黑洞文件,所有寫入他的數據都會消失,
//從他里面讀不出數據
if ((fd = open("/dev/null", O_RDWR, 0)) != -1) {
dup2(fd, STDIN_FILENO); //將標准輸入重定向到fd
dup2(fd, STDOUT_FILENO); //將標准輸出重定向到fd
dup2(fd, STDERR_FILENO); //將錯誤輸出重定向到fd
if (fd > STDERR_FILENO)
close(fd);
}
}
着重解釋下dup2這個函數
int dup(int oldfd);
復制oldfd所指向的文件描述符,返回系統目前未使用的最小的文件描述符。
新的文件描述符和oldfd指向一個文件,
他們共享讀寫,枷鎖等權限,當一個文件描述符操作lseek,另一個也會隨着偏移。
但是他們不共享close-on-exec。
int dup2(int oldfd, int newfd);
復制oldfd到newfd,如果newfd指向的文件打開,那么會關閉該文件。
dup2失敗后返回-1,成功則共享文件狀態。
接下來看看initServer函數做了些什么 下面是initServer內部的幾個步驟
//由於initserver是守護進程,忽略sighup
//sighub在控制終端關閉的時候會發給session首進程
//session首進程退出時,該信號被發送到該session中的前台進程組中的每一個進程
signal(SIGHUP, SIG_IGN);
//寫管道發現讀進程終止時產生sigpipe信號,
//寫已終止的SOCK_STREAM套接字同樣會產生此信號
signal(SIGPIPE, SIG_IGN);
server.pid = getpid();
server.current_client = NULL;
server.clients = listCreate(); //創建客戶隊列
server.clients_to_close = listCreate(); //創建關閉隊列
server.slaves = listCreate(); //創建從機隊列
server.monitors = listCreate(); //創建監控隊列
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
server.unblocked_clients = listCreate(); //創建非堵塞客戶隊列
server.ready_keys = listCreate(); //創建可讀鍵隊列
server.clients_waiting_acks = listCreate(); //客戶等待回包隊列
server.get_ack_from_slaves = 0;
server.clients_paused = 0;
//創建別的模塊公用的對象
createSharedObjects(); //創建共享對象
adjustOpenFilesLimit(); //改變可打開文件的最大數量
可以看看adjustOpenFilesLimit() 里邊做了什么
void adjustOpenFilesLimit(void) {
rlim_t maxfiles = server.maxclients+REDIS_MIN_RESERVED_FDS;
struct rlimit limit;
//獲取每個進程能創建的各種系統資源的最大數量
if (getrlimit(RLIMIT_NOFILE,&limit) == -1) {
redisLog(REDIS_WARNING,"Unable to obtain the current NOFILE limit (%s),
assuming 1024 and setting the max clients configuration accordingly.",
strerror(errno));
//1024 - 目前redis最小保留的用於其他功能的描述符
server.maxclients = 1024-REDIS_MIN_RESERVED_FDS;
} else {
//系統當前(軟)限制
rlim_t oldlimit = limit.rlim_cur;
/* Set the max number of files if the current limit is not enough
* for our needs. */
if (oldlimit < maxfiles) {
//最大軟件限制
rlim_t bestlimit;
int setrlimit_error = 0;
/* Try to set the file limit to match 'maxfiles' or at least
* to the higher value supported less than maxfiles. */
//嘗試設置限制數符合maxfiles或者最接近
bestlimit = maxfiles;
//循環判斷,如果最大的連接數大於當前系統能使用的最大軟件限制
while(bestlimit > oldlimit) {
//設置每次遞減的數量
rlim_t decr_step = 16;
//設置當前軟件限制
limit.rlim_cur = bestlimit;
limit.rlim_max = bestlimit;
//設置成功則斷開,不成功繼續循環,找到最接近的最大限制
if (setrlimit(RLIMIT_NOFILE,&limit) != -1) break;
//設置限制錯誤碼
setrlimit_error = errno;
/* We failed to set file limit to 'bestlimit'. Try with a
* smaller limit decrementing by a few FDs per iteration. */
//最大限制遞減,如果小於規定值那么退出循環
if (bestlimit < decr_step) break;
bestlimit -= decr_step;
/* Assume that the limit we get initially is still valid if
* our last try was even lower. */
//最大連接數小於當前系統允許的資源數量,那么擴充為系統允許的數量
if (bestlimit < oldlimit) bestlimit = oldlimit;
//這個條件用於處理最大連接數
if (bestlimit < maxfiles) {
//保存當前服務器客戶隊列最大數量
int old_maxclients = server.maxclients;
//服務器客戶隊列最大數量
server.maxclients = bestlimit-REDIS_MIN_RESERVED_FDS;
if (server.maxclients < 1) {
redisLog(REDIS_WARNING,"Your current 'ulimit -n' "
"of %llu is not enough for Redis to start. "
"Please increase your open file limit to at least "
"%llu. Exiting.",
(unsigned long long) oldlimit,
(unsigned long long) maxfiles);
exit(1);
}
}
回到initServer函數內部
adjustOpenFilesLimit()函數過后是調用
server.el = aeCreateEventLoop(server.maxclients+REDIS_EVENTLOOP_FDSET_INCR);
這個函數是創建基本的時間循環結構。這個api寫在Ae.c中,這個文件主要負責創建事件輪詢
的結構,創建文件讀寫事件,刪除文件讀寫事件,刪除事件輪詢結構,派發文件讀寫功能等,下一屆再講。
然后是創建了一個定時器回調函數
if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
redisPanic("Can't create the serverCron time event.");
exit(1);
}
接着創建了 TCP的回調函數,主要用於有新的連接到來觸發acceptTcpHandler
for (j = 0; j < server.ipfd_count; j++) {
if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE,
acceptTcpHandler,NULL) == AE_ERR)
{
redisPanic(
"Unrecoverable error creating server.ipfd file event.");
}
}
//然后添加了udp的回調
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) redisPanic("Unrecoverable error creating server.sofd file event.");
這就是initServer大體的流程
下一節我們詳細講一下eventloop結構和其中封裝的幾種網絡模型。
我的微信號