Nginx事件驅動框架

最初的Web服務器如Apache,採用的是fork and run的模式,對每一個到來的鏈接,fork一個進程去處理,處理完成後進程退出。優勢是編程實現簡單,缺點是併發處理能力不足。爲應對高併發的處理,以Nginx爲表明的異步處理方式應運而生。node

Nginx以事件驅動工做,事件的來源有兩個,網絡IO和定時器。linux

對於高併發的網絡IO處理,不一樣的操做系統提供了不一樣的解決方案,如linux的epoll, freebsd的kqueue。這裏以epoll爲例。將須要監聽的socket加入到epoll中後,即可以經過epoll_wait獲取已發生的事件,避免對衆多的socket進行輪尋。編程

Nginx的定時器採用紅黑數實現,每一個定時器事件以超時時間爲key插入到紅黑樹中,每次取紅黑樹中key最小的結點與當前的系統時間比較便可知道是否超時。服務器

Nginx工做進程處理任務的核心在ngx_process_events_and_timers函數中。網絡

for ( ;; ) {

        ...................

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");

        ngx_process_events_and_timers(cycle);

        ...................
    }

ngx_process_events_and_timers主要邏輯以下所示。併發

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{


    (void) ngx_process_events(cycle, timer, flags);

    ngx_event_process_posted(cycle, &ngx_posted_accept_events);

    
    ngx_event_expire_timers();

    ngx_event_process_posted(cycle, &ngx_posted_events);
}

函數中先調用ngx_process_events處理epoll_wait獲得的網絡IO事件,再調用ngx_event_expire_timers處理全部的超時事件。異步

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;


    events = epoll_wait(ep, event_list, (int) nevents, timer);

    for (i = 0; i < events; i++) {
        c = event_list[i].data.ptr;
        rev = c->read;

        revents = event_list[i].events;


        if ((revents & EPOLLIN) && rev->active) {
             rev->handler(rev);
        }

        wev = c->write;

        if ((revents & EPOLLOUT) && wev->active) {

             wev->handler(wev);
        }
    }

    return NGX_OK;
}

利用epoll模式時ngx_process_events即爲ngx_epoll_process_events,上面的代碼中省略了無關的部分,當revents & EPOLLIN爲true時socket有數據能夠讀取,revents & EPOLLOUT爲true時socket可寫,而後調用相應的handler回調函數進行處理。socket

ngx_event_expire_timers處理定時器事件邏輯更簡單一些,主要就是調用ngx_rbtree_min獲取超時時間最近的事件,若是已超時即調用ev->handler進行處理。函數

void
ngx_event_expire_timers(void)
{
    for ( ;; ) {
        root = ngx_event_timer_rbtree.root;

        if (root == sentinel) {
            return;
        }

        node = ngx_rbtree_min(root, sentinel);

        /* node->key > ngx_current_time */
        if ((ngx_msec_int_t) (node->key - ngx_current_msec) > 0) {
            return;
        }

        ev->timer_set = 0;

        ev->timedout = 1;

        ev->handler(ev);
    }
}

 

對於網絡IO主要操做就是讀和寫,現實網絡環境很是複雜,鏈接會由於各類緣由中斷,沒法傳輸數據。同時服務器端的每一個網絡鏈接都要消耗服務器資源,爲了不無效的鏈接一直佔用系用的資源,須要對讀寫操做設置超時機制,爲此Nginx作了專門的處理。高併發

對每一個到來的連接,Nginx分配一個ngx_connection_t結構體存儲相應的信息,每一個ngx_connection_t結構體都有兩個成員read和write,對應兩個ngx_event_t事件。當須要從socket讀取數據時,將socket加入到epoll監聽事件中,同時將對應的read事件加入到定時器中,若是定時器超時後仍然沒有數據可讀,便認爲讀取數據超時。

事件超時時,Nginx會將ev->timedout置爲1,再調用ev->handler。相應的Nginx中對每一個事件的handler回調函數大多會有以下的邏輯

if (ev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_close_connection(c);
        return;
    }

handler回調函數的開頭判斷事件是否已超時,若是ev->timedout不爲0便認爲已超時。可能會關閉鏈接,也可能會執行其餘操做。

相關文章
相關標籤/搜索