Nginx學習之路(四)NginX的子進程主循環

在上一篇文章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上的所有請求失敗,不過不會影響到所有請求,所以降低了風險。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章