### event的類型和功能?
### [](http://tengine.taobao.org/book/chapter_06.html#id2 "永久鏈接至標題")
Nginx是以event(事件)處理模型為基礎的模塊。它為了支持跨平臺,抽象出了event模塊。它支持的event處理類型有:AIO(異步IO),/dev/poll(Solaris 和Unix特有),epoll(Linux特有),eventport(Solaris 10特有),kqueue(BSD特有),poll,rtsig(實時信號),select等。
event模塊的主要功能就是,監聽accept后建立的連接,對讀寫事件進行添加刪除。事件處理模型和Nginx的非阻塞IO模型結合在一起使用。當IO可讀可寫的時候,相應的讀寫事件就會被喚醒,此時就會去處理事件的回調函數。
特別對于Linux,Nginx大部分event采用epoll EPOLLET(邊沿觸發)的方法來觸發事件,只有listen端口的讀事件是EPOLLLT(水平觸發)。對于邊沿觸發,如果出現了可讀事件,必須及時處理,否則可能會出現讀事件不再觸發,連接餓死的情況。
[](http:// "點擊提交Issue,反饋你的意見...")
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 (*process_changes)(ngx_cycle_t *cycle, ngx_uint_t nowait);
/* 處理事件的函數 */
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;
上述是event處理抽象出來的關鍵結構體,可以看到,每個event處理模型,都需要實現部分功能。最關鍵的是add和del功能,就是最基本的添加和刪除事件的函數。
[](http:// "點擊提交Issue,反饋你的意見...")
### accept鎖?[](http://tengine.taobao.org/book/chapter_06.html#accept-40 "永久鏈接至標題")
Nginx是多進程程序,80端口是各進程所共享的,多進程同時listen 80端口,勢必會產生競爭,也產生了所謂的“驚群”效應。當內核accept一個連接時,會喚醒所有等待中的進程,但實際上只有一個進程能獲取連接,其他的進程都是被無效喚醒的。所以Nginx采用了自有的一套accept加鎖機制,避免多個進程同時調用accept。Nginx多進程的鎖在底層默認是通過CPU自旋鎖來實現。如果操作系統不支持自旋鎖,就采用文件鎖。
Nginx事件處理的入口函數是ngx_process_events_and_timers(),下面是部分代碼,可以看到其加鎖的過程:
[](http:// "點擊提交Issue,反饋你的意見...")
if (ngx_use_accept_mutex) {
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
return;
}
if (ngx_accept_mutex_held) {
flags |= NGX_POST_EVENTS;
} else {
if (timer == NGX_TIMER_INFINITE
|| timer > ngx_accept_mutex_delay)
{
timer = ngx_accept_mutex_delay;
}
}
}
}
在ngx_trylock_accept_mutex()函數里面,如果拿到了鎖,Nginx會把listen的端口讀事件加入event處理,該進程在有新連接進來時就可以進行accept了。注意accept操作是一個普通的讀事件。下面的代碼說明了這點:
[](http:// "點擊提交Issue,反饋你的意見...")
(void) ngx_process_events(cycle, timer, flags);
if (ngx_posted_accept_events) {
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
}
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
ngx_process_events()函數是所有事件處理的入口,它會遍歷所有的事件。搶到了accept鎖的進程跟一般進程稍微不同的是,它被加上了NGX_POST_EVENTS標志,也就是說在ngx_process_events() 函數里面只接受而不處理事件,并加入post_events的隊列里面。直到ngx_accept_mutex鎖去掉以后才去處理具體的事件。為什么這樣?因為ngx_accept_mutex是全局鎖,這樣做可以盡量減少該進程搶到鎖以后,從accept開始到結束的時間,以便其他進程繼續接收新的連接,提高吞吐量。
ngx_posted_accept_events和ngx_posted_events就分別是accept延遲事件隊列和普通延遲事件隊列。可以看到ngx_posted_accept_events還是放到ngx_accept_mutex鎖里面處理的。該隊列里面處理的都是accept事件,它會一口氣把內核backlog里等待的連接都accept進來,注冊到讀寫事件里。
而ngx_posted_events是普通的延遲事件隊列。一般情況下,什么樣的事件會放到這個普通延遲隊列里面呢?我的理解是,那些CPU耗時比較多的都可以放進去。因為Nginx事件處理都是根據觸發順序在一個大循環里依次處理的,因為Nginx一個進程同時只能處理一個事件,所以有些耗時多的事件會把后面所有事件的處理都耽擱了。
除了加鎖,Nginx也對各進程的請求處理的均衡性作了優化,也就是說,如果在負載高的時候,進程搶到的鎖過多,會導致這個進程被禁止接受請求一段時間。
比如,在ngx_event_accept函數中,有類似代碼:
[](http:// "點擊提交Issue,反饋你的意見...")
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
ngx_cycle->connection_n是進程可以分配的連接總數,ngx_cycle->free_connection_n是空閑的進程數。上述等式說明了,當前進程的空閑進程數小于1/8的話,就會被禁止accept一段時間。
[](http:// "點擊提交Issue,反饋你的意見...")
### 定時器?[](http://tengine.taobao.org/book/chapter_06.html#id3 "永久鏈接至標題")
Nginx在需要用到超時的時候,都會用到定時器機制。比如,建立連接以后的那些讀寫超時。Nginx使用紅黑樹來構造定期器,紅黑樹是一種有序的二叉平衡樹,其查找插入和刪除的復雜度都為O(logn),所以是一種比較理想的二叉樹。
定時器的機制就是,二叉樹的值是其超時時間,每次查找二叉樹的最小值,如果最小值已經過期,就刪除該節點,然后繼續查找,直到所有超時節點都被刪除。
- 上篇:nginx模塊開發篇
- nginx平臺初探
- 初探nginx架構
- nginx基礎概念
- connection
- request
- keepalive
- pipe
- lingering_close
- 基本數據結構
- ngx_str_t
- ngx_pool_t
- ngx_array_t
- ngx_hash_t
- ngx_hash_wildcard_t
- ngx_hash_combined_t
- ngx_hash_keys_arrays_t
- ngx_chain_t
- ngx_buf_t
- ngx_list_t
- ngx_queue_t
- nginx的配置系統
- 指令參數
- 指令上下文
- nginx的模塊化體系結構
- 模塊的分類
- nginx的請求處理
- handler模塊
- handler模塊簡介
- 模塊的基本結構
- 模塊配置結構
- 模塊配置指令
- 模塊上下文結構
- 模塊的定義
- handler模塊的基本結構
- handler模塊的掛載
- handler的編寫步驟
- 示例: hello handler 模塊
- handler模塊的編譯和使用
- 更多handler模塊示例分析
- http access module
- http static module
- http log module
- 過濾模塊
- 過濾模塊簡介
- 過濾模塊的分析
- upstream模塊
- upstream模塊
- upstream模塊接口
- memcached模塊分析
- 本節回顧
- 負載均衡模塊
- 配置
- 指令
- 鉤子
- 初始化配置
- 初始化請求
- peer.get和peer.free回調函數
- 本節回顧
- 其他模塊
- core模塊
- event模塊
- 模塊開發高級篇
- 變量
- 下篇:nginx原理解析篇
- nginx架構詳解
- nginx的源碼目錄結構
- nginx的configure原理
- 模塊編譯順序
- nginx基礎設施
- 內存池
- nginx的啟動階段
- 概述
- 共有流程
- 配置解析
- nginx的請求處理階段
- 接收請求流程
- http請求格式簡介
- 請求頭讀取
- 解析請求行
- 解析請求頭
- 請求體讀取
- 讀取請求體
- 丟棄請求體
- 多階段處理請求
- 多階段執行鏈
- POST_READ階段
- SERVER_REWRITE階段
- FIND_CONFIG階段
- REWRITE階段
- POST_REWRITE階段
- PREACCESS階段
- ACCESS階段
- POST_ACCESS階段
- TRY_FILES階段
- CONTENT階段
- LOG階段
- Nginx filter
- header filter分析
- body filter分析
- ngx_http_copy_filter_module分析
- ngx_http_write_filter_module分析
- subrequest原理解析
- https請求處理解析
- 附錄A 編碼風格
- 附錄B 常用API
- 附錄C 模塊編譯,調試與測試