標籤:style blog http color 使用 os io for
轉自:煙雨江南
Nginx事件管理主要是網路事件和定時器事件。下面介紹定時器事件管理,即逾時管理。
為什麼進行逾時管理?
Nginx有必要對可能發生逾時的事件進行統一管理,並在事件逾時時作出相應的處理,比如回收資源,返回錯誤等。舉例來說,當用戶端對nginx發出請求串連後,nginx會accept()並建立對應的連線物件、讀取請求的頭部資訊。而讀取這個頭部資訊顯然是要在一定的時間內完成的。如果在一個有限的時間內沒有讀取到頭部資訊或者讀取的頭部資訊不完整,那麼nginx就無法進行正常處理,並且認為這是一個錯誤/非法的請求,直接返回錯誤資訊並釋放相應資源,如果nginx不這樣做,那麼針對如此的惡意攻擊就很容易實施。
如何進行逾時管理?
對於逾時管理,要解決兩個問題:
(1)逾時事件對象的組織
nginx採用的是紅/黑樹狀結構。(2)逾時事件對象的逾時檢測有兩種方案:一種是定時檢測機制,通過設定定時器,沒過一定時間就對紅/黑樹狀結構管理的所有逾時事件進行一次超級掃描。另一種是先計算出距離當前最快發生逾時的時間是多久。然後等待這個時間之後再去進行一次逾時檢測。 逾時事件對象的組織
Nginx設定了兩個全域變數以便在程式的任何地方都可以哀訴的訪問到這棵紅棵樹(src/event/ngx_event_timer.c):
ngx_thread_volatile ngx_rbtree_t ngx_event_timer_rbtree;//逾時管理的紅/黑樹狀結構結構static ngx_rbtree_node_t ngx_event_timer_sentinel;//紅/黑樹狀結構中的哨兵節點
紅/黑樹狀結構結構的初始化紅/黑樹狀結構的初始化函數ngx_event_timer_init()是在函數ngx_event_process_init()函數內被調用的。即每個worker進程會在自身的初始化時建立這棵紅/黑樹狀結構。原始碼(src/eventngx_event.c):
static ngx_int_tngx_event_process_init(ngx_cycle_t *cycle){ .... if (ngx_event_timer_init(cycle->log) == NGX_ERROR) { return NGX_ERROR; } ...}
函數ngx_event_timer_init完成了紅/黑樹狀結構結構的初始化:
/* * the event timer rbtree may contain the duplicate keys, however, * it should not be a problem, because we use the rbtree to find * a minimum timer value only */ngx_int_tngx_event_timer_init(ngx_log_t *log){ //紅/黑樹狀結構初始化 ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel, ngx_rbtree_insert_timer_value); //多執行緒#if (NGX_THREADS) if (ngx_event_timer_mutex) { ngx_event_timer_mutex->log = log; return NGX_OK; } ngx_event_timer_mutex = ngx_mutex_init(log, 0); if (ngx_event_timer_mutex == NULL) { return NGX_ERROR; }#endif return NGX_OK;}
而ngx_rbtree_init(tree, s, i) 是宏定義(src/core/ngx_rbtree.h),即建立了一棵空的紅/黑樹狀結構:
#define ngx_rbtree_init(tree, s, i) ngx_rbtree_sentinel_init(s); (tree)->root = s; (tree)->sentinel = s; (tree)->insert = i
對事件進行逾時監控:
當需要對某個事件進行逾時監控時,就會把它加入到這個紅/黑樹狀結構內。比如,nginx調用accept接受到用戶端的請求並建立對應的連線物件connection後,在連線物件的初始化函數ngx_http_init_connection()內,可以找到這樣的代碼:
voidngx_http_init_connection(ngx_connection_t *c){...(358L)ngx_add_timer(rev, c->listening->post_accept_timeout);...}
ngx_add_timer的第一個參數是事件對象,第二個參數是逾時時限。
逾時檢測:Nginx具體使用哪一種逾時檢測方案取決於配置項timer_resolution,比如:timer_resolution 100ms;反應到nginx代碼中就是全域變數ngx_timer_resolution的值為100;下面我們看worker進程的核心處理函數ngx_process_events_and_timers(ngx_event.c):
voidngx_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;...(void) ngx_process_events(cycle, timer, flags);...}可以看到,ngx_timer_resolution是否為0影響了兩個值timer和flags。
當ngx_timer_resolution不為0時,即方案1.timer為無限大。timer在函數ngx_process_events()內被用作事件機制被阻塞的最長時間。那麼timer為無限大會不會導致事件處理機制無限等待而逾時事件得不到及時處理呢?不會。因為正常情況下事件處理機制會監控到某些I/O事件的發生。即便是伺服器太閑,沒有任何I/O事件發生,背景工作處理序也不會無限等待。因為背景工作處理序一開始就設定好了一個定時器,這實現在初始化函數ngx_event_process_init()內,看代碼:
static ngx_int_tngx_event_process_init(ngx_cycle_t *cycle){ ... sa.sa_handler = ngx_timer_signal_handler; sigemptyset(&sa.sa_mask); itv.it_interval.tv_sec = ngx_timer_resolution / 1000; itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000; itv.it_value.tv_sec = ngx_timer_resolution / 1000; itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000; if (setitimer(ITIMER_REAL, &itv, NULL) == -1) { ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, "setitimer() failed"); } ....}
回呼函數ngx_timer_signal_handler:
static voidngx_timer_signal_handler(int signo){ ngx_event_timer_alarm = 1;#if 1 ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal");#endif}
可以看出它僅僅是將標誌變數ngx_event_timer_alarm 設定為1.
只有在ngx_event_timer_alarm 為1 的情況下,背景工作處理序才會更新時間。函數ngx_epoll_process_events中的代碼(ngx_epoll_module.c):
static ngx_int_tngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags){... 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(); }...}
在 方案一的情況下,||前面的式子為假,那麼ngx_event_timer_alarm 不為1 的情況下,更新函數ngx_time_update()不會被執行。那麼會導致逾時檢測函數ngx_event_expire_timers不會被執行。 看ngx_process_events_and_timers函數的代碼(ngx_event.c):
voidngx_process_events_and_timers(ngx_cycle_t *cycle){... delta = ngx_current_msec; (void) ngx_process_events(cycle, timer, flags);//事件處理函數 delta = ngx_current_msec - delta;...if (delta) { ngx_event_expire_timers();//逾時檢測函數 }...}
當ngx_timer_resolution為0時,執行方案2。timer設定為最快發生逾時的事件對象的逾時時刻與當前時刻的時間差。具體計算時在函數ngx_event_find_timer內(ngx_event_timer.c)。
ngx_msec_tngx_event_find_timer(void){ ngx_msec_int_t timer; ngx_rbtree_node_t *node, *root, *sentinel; if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) { return NGX_TIMER_INFINITE; } ngx_mutex_lock(ngx_event_timer_mutex); root = ngx_event_timer_rbtree.root; sentinel = ngx_event_timer_rbtree.sentinel; node = ngx_rbtree_min(root, sentinel); ngx_mutex_unlock(ngx_event_timer_mutex); timer = (ngx_msec_int_t) (node->key - ngx_current_msec); return (ngx_msec_t) (timer > 0 ? timer : 0);}
該函數從紅/黑樹狀結構中找到key值最小的節點,然後用key值減去當前時刻即得到預期timer值。這個值可能是負數,表示已經有事件逾時了。因此直接將其設定 為0.那麼事件處理 機制在開始監控I/O事件時會立即返回,以便馬上處理這些逾時事件。同時flags被設定為NGX_UPDATE_TIME。從 ngx_epoll_process_events函數的代碼中可以看出ngx_time_update()將被執行,事件被更新。即事件處理機制每次返 回都會更新時間。如果I/O事件比較多,那麼會導致比較頻繁地調用gettimeofday()系統函數,這也可以說是逾時檢測方案2對效能的最大影響。 這個時候逾時檢測函數ngx_event_expire_timers()函數會被執行。
下面看看逾時檢測函數ngx_event_expire_timers。其完成的主要任務是對逾時事件對象是否逾時進行掃描檢測以及對已逾時事件對象的處理。檢測是否有事件對象逾時不需遍曆掃描所有逾時時間對象,而是找到最近的即將逾時的逾時事件對象。判斷其是否逾時,如果逾時,則將其移出紅/黑樹狀結構,設定逾時標記,調用回呼函數進行處理。之後再判斷第二近的即將逾時的逾時事件對象,如此反覆,知道某個逾時事件對象還未逾時或所有逾時事件對象都已逾時並處理完畢就結束檢測。
下面是其核心代碼:
voidngx_event_expire_timers(void){ ngx_event_t *ev; ngx_rbtree_node_t *node, *root, *sentinel; sentinel = ngx_event_timer_rbtree.sentinel; //迴圈檢測 for ( ;; ) { ngx_mutex_lock(ngx_event_timer_mutex); 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) { ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer)); ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, "event timer del: %d: %M", ngx_event_ident(ev->data), ev->timer.key); //從紅/黑樹狀結構中移除這個已逾時的逾時事件對象 ngx_rbtree_delete(&ngx_event_timer_rbtree, &ev->timer); ngx_mutex_unlock(ngx_event_timer_mutex);#if (NGX_DEBUG) ev->timer.left = NULL; ev->timer.right = NULL; ev->timer.parent = NULL;#endif //標記:是否已加入紅/黑樹狀結構逾時管理 ev->timer_set = 0; //標記:是否逾時 ev->timedout = 1; //調用回呼函數 ev->handler(ev); continue; } break; } ngx_mutex_unlock(ngx_event_timer_mutex);}