在上一篇文章Nginx學習之路(三)NginX的子進程生產過程中說道了生產子進程過程中的proc處理過程,也就是這段代碼:
//調用傳入的回調函數,子進程的正式主循環開始,回調函數的實體是ngx_worker_process_cycle
proc(cycle, data);
今天就來介紹一下這個proc的具體過程:
首先,proc函數是一個隨ngx_spawn_process()函數傳入的回調,子進程的實體函數是ngx_worker_process_cycle(),該函數的實體過程如下:
static void
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
ngx_int_t worker = (intptr_t) data;
//在master中,ngx_process被設置爲NGX_PROCESS_MASTER
ngx_process = NGX_PROCESS_WORKER;
ngx_worker = worker;
//初始化
ngx_worker_process_init(cycle, worker);
ngx_setproctitle("worker process");
//主循環開始
for ( ;; ) {
//如果進程退出,關閉所有連接
if (ngx_exiting) {
if (ngx_event_no_timers_left() == NGX_OK) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
ngx_worker_process_exit(cycle);
}
}
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
//處理事件和timer,這裏也是我們要着重瞭解的函數
ngx_process_events_and_timers(cycle);
//收到NGX_CMD_TERMINATE命令
if (ngx_terminate) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
//清理後進程退出,會調用所有模塊的鉤子exit_process
ngx_worker_process_exit(cycle);
}
//收到NGX_CMD_QUIT命令
if (ngx_quit) {
ngx_quit = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"gracefully shutting down");
ngx_setproctitle("worker process is shutting down");
//如果進程沒有"正在退出"
if (!ngx_exiting) {
//關閉監聽socket,設置退出正在狀態
ngx_exiting = 1;
ngx_set_shutdown_timer(cycle);
ngx_close_listening_sockets(cycle);
ngx_close_idle_connections(cycle);
}
}
//收到NGX_CMD_REOPEN命令,重新打開log
if (ngx_reopen) {
ngx_reopen = 0;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
ngx_reopen_files(cycle, -1);
}
}
}
下面就要詳細的說明ngx_process_events_and_timers(cycle)這個過程,代碼如下:
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;
#if (NGX_WIN32)
/* handle signals from master in case of network inactivity */
if (timer == NGX_TIMER_INFINITE || timer > 500) {
timer = 500;
}
#endif
}
/*
ngx_use_accept_mutex變量代表是否使用accept互斥體
默認是使用,可以通過accept_mutex off;指令關閉;
accept mutex 的作用就是避免驚羣,同時實現負載均衡
*/
if (ngx_use_accept_mutex) {
/*
ngx_accept_disabled變量在ngx_event_accept函數中計算。
如果ngx_accept_disabled大於0,就表示該進程接受的鏈接過多,
因此放棄一次爭搶accept mutex的機會,同時將自己減一。
然後,繼續處理已有連接上的事件。
nginx就利用這一點實現了繼承關於連接的基本負載均衡。
*/
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
/*
嘗試鎖accept mutex,只有成功獲取鎖的進程,纔會將listen套接字放到epoll中。
因此,這就保證了只有一個進程擁有監聽套接口,故所有進程阻塞在epoll_wait時,
纔不會驚羣現象。
*/
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
if (ngx_accept_mutex_held) {
/*
如果進程獲得了鎖,將添加一個 NGX_POST_EVENTS 標誌。
這個標誌的作用是將所有產生的事件放入一個隊列中,等釋放後,在慢慢來處理事件。
因爲,處理時間可能會很耗時,如果不先施放鎖再處理的話,該進程就長時間霸佔了鎖,
導致其他進程無法獲取鎖,這樣accept的效率就低了。
*/
flags |= NGX_POST_EVENTS;
} else {
/*
沒有獲得所得進程,當然不需要NGX_POST_EVENTS標誌。
但需要設置延時多長時間,再去爭搶鎖。
*/
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
delta = ngx_current_msec;
/*接下來,epoll要開始wait事件,
ngx_process_events的具體實現是對應到epoll模塊中的ngx_epoll_process_events函數,這個過程在在下面也要詳細的說明一下
*/
(void) ngx_process_events(cycle, timer, flags);
//統計本次wait事件的耗時
delta = ngx_current_msec - delta;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"timer delta: %M", delta);
/*
ngx_posted_accept_events是一個事件隊列,暫存epoll從監聽套接口wait到的accept事件。
前文提到的NGX_POST_EVENTS標誌被使用後,會將所有的accept事件暫存到這個隊列
*/
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
//所有accept事件處理完之後,如果持有鎖的話,就釋放掉。
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
/*
delta是之前統計的耗時,存在毫秒級的耗時,就對所有時間的timer進行檢查,
如果timeout 就從time rbtree中刪除到期的timer,同時調用相應事件的handler函數處理
*/
if (delta) {
ngx_event_expire_timers();
}
/*
處理普通事件(連接上獲得的讀寫事件),
因爲每個事件都有自己的handler方法,
*/
ngx_event_process_posted(cycle, &ngx_posted_events);
}
然後就要介紹(void) ngx_process_events(cycle, timer, flags)這個過程了,有的人喜歡稱這個爲鉤子函數,在我的理解他就是一個回調函數,這個函數的定義如下:
#define ngx_process_events ngx_event_actions.process_events
注意這個ngx_event_actions,這個就是實現io複用的主要的結構體,它是一個全局變量,定義如下
ngx_event_actions_t ngx_event_actions;
typedef struct {
ngx_int_t (*add)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*del)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*enable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*disable)(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags);
ngx_int_t (*add_conn)(ngx_connection_t *c);
ngx_int_t (*del_conn)(ngx_connection_t *c, ngx_uint_t flags);
ngx_int_t (*notify)(ngx_event_handler_pt handler);
ngx_int_t (*process_events)(ngx_cycle_t *cycle, ngx_msec_t timer,
ngx_uint_t flags);
ngx_int_t (*init)(ngx_cycle_t *cycle, ngx_msec_t timer);
void (*done)(ngx_cycle_t *cycle);
} ngx_event_actions_t;
然後在ngx_epoll_module.c和ngx_poll_module.c以及ngx_select_module.c中實現了關於epoll,poll,select這幾種io複用方式的具體方法,下面以epoll爲例,在ngx_epoll_module.c中:
ngx_event_actions = ngx_epoll_module_ctx.actions;
static ngx_event_module_t ngx_epoll_module_ctx = {
&epoll_name,
ngx_epoll_create_conf, /* create configuration */
ngx_epoll_init_conf, /* init configuration */
{
ngx_epoll_add_event, /* add an event */
ngx_epoll_del_event, /* delete an event */
ngx_epoll_add_event, /* enable an event */
ngx_epoll_del_event, /* disable an event */
ngx_epoll_add_connection, /* add an connection */
ngx_epoll_del_connection, /* delete an connection */
#if (NGX_HAVE_EVENTFD)
ngx_epoll_notify, /* trigger a notify */
#else
NULL, /* trigger a notify */
#endif
ngx_epoll_process_events, /* process the events */
ngx_epoll_init, /* init the events */
ngx_epoll_done, /* done the events */
}
};
這幾種add,del等事件就不具體贅述了,詳細說明下ngx_epoll_process_events的過程:
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
int events;
uint32_t revents;
ngx_int_t instance, i;
ngx_uint_t level;
ngx_err_t err;
ngx_event_t *rev, *wev;
ngx_queue_t *queue;
ngx_connection_t *c;
/* NGX_TIMER_INFINITE == INFTIM */
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll timer: %M", timer);
//調用epoll_wait獲取事件
events = epoll_wait(ep, event_list, (int) nevents, timer);
err = (events == -1) ? ngx_errno : 0;
//更新時間
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
ngx_time_update();
}
//epoll_wait出錯處理
if (err) {
if (err == NGX_EINTR) {
if (ngx_event_timer_alarm) {
ngx_event_timer_alarm = 0;
return NGX_OK;
}
level = NGX_LOG_INFO;
} else {
level = NGX_LOG_ALERT;
}
ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
return NGX_ERROR;
}
//本次調用沒有事件發生
if (events == 0) {
if (timer != NGX_TIMER_INFINITE) {
return NGX_OK;
}
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"epoll_wait() returned no events without timeout");
return NGX_ERROR;
}
//遍歷本次epoll_wait返回的所有事件
for (i = 0; i < events; i++) {
//獲取連接ngx_connection_t的地址
c = event_list[i].data.ptr;
//連接的地址最後一位具有特殊意義:用於存儲instance變量,將其取出來
instance = (uintptr_t) c & 1;
//無論是32位還是64位機器,其地址最後一位一定是0,獲取真正地址
c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
//取出讀事件
rev = c->read;
//判斷讀事件是否爲過期事件
if (c->fd == -1 || rev->instance != instance) {
/*
* the stale event from a file descriptor
* that was just closed in this iteration
*/
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: stale event %p", c);
continue;
}
//取出事件類型
revents = event_list[i].events;
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: fd:%d ev:%04XD d:%p",
c->fd, revents, event_list[i].data.ptr);
if (revents & (EPOLLERR|EPOLLHUP)) {
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll_wait() error on fd:%d ev:%04XD",
c->fd, revents);
/*
* if the error events were returned, add EPOLLIN and EPOLLOUT
* to handle the events at least in one active handler
*/
revents |= EPOLLIN|EPOLLOUT;
}
#if 0
if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"strange epoll_wait() events fd:%d ev:%04XD",
c->fd, revents);
}
#endif
//是讀事件且該事件是活躍的
if ((revents & EPOLLIN) && rev->active) {
#if (NGX_HAVE_EPOLLRDHUP)
if (revents & EPOLLRDHUP) {
rev->pending_eof = 1;
}
rev->available = 1;
#endif
rev->ready = 1;
//事件需要延後處理
if (flags & NGX_POST_EVENTS) {
/*如果要在post隊列中延後處理該事件,首先要判斷它是新連接時間還是普通事件
以確定是把它加入到ngx_posted_accept_events隊列或者ngx_posted_events隊列中。*/
queue = rev->accept ? &ngx_posted_accept_events
: &ngx_posted_events;
//將該事件添加到相應的延後隊列中
ngx_post_event(rev, queue);
} else {
//立即調用事件回調方法來處理這個事件
rev->handler(rev);
}
}
wev = c->write;
if ((revents & EPOLLOUT) && wev->active) {
if (c->fd == -1 || wev->instance != instance) {
/*
* the stale event from a file descriptor
* that was just closed in this iteration
*/
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: stale event %p", c);
continue;
}
wev->ready = 1;
#if (NGX_THREADS)
wev->complete = 1;
#endif
if (flags & NGX_POST_EVENTS) {
ngx_post_event(wev, &ngx_posted_events);
} else {
wev->handler(wev);
}
}
}
return NGX_OK;
}
總結一下,worker進程的主要任務就是通過io複用的方式,處理基本的網絡事件,多個worker進程之間是對等的,他們同等競爭來自客戶端的請求,各進程互相之間是獨立的。一個請求,只可能在一個worker進程中處理,一個worker進程,不可能處理其它進程的請求。nginx的這種多進程處理模式好處肯定會很多了,首先,對於每個worker進程來說,獨立的進程,不需要加鎖,所以省掉了鎖帶來的開銷,同時在編程以及問題查找時,也會方便很多。其次,採用獨立的進程,可以讓互相之間不會影響,一個進程退出後,其它進程還在工作,服務不會中斷,master進程則很快啓動新的worker進程。當然,worker進程的異常退出,肯定是程序有bug了,異常退出,會導致當前worker上的所有請求失敗,不過不會影響到所有請求,所以降低了風險。