分析Nginx epoll高效事件模型
首先Nginx支援以下這些事件模型:
Nginx支援如下處理連線的方法(I/O複用方法),這些方法可以通過use指令指定。
* select – 標準方法。 如果當前平臺沒有更有效的方法,它是編譯時預設的方法。你可以使用配置引數 –with-select_module 和 –without-select_module 來啟用或禁用這個模組。
* poll – 標準方法。 如果當前平臺沒有更有效的方法,它是編譯時預設的方法。你可以使用配置引數 –with-poll_module 和 –without-poll_module 來啟用或禁用這個模組。
* kqueue – 高效的方法,使用於 FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0 和 MacOS X. 使用雙處理器的MacOS X系統使用kqueue可能會造成核心崩潰。
* epoll – 高效的方法,使用於Linux核心2.6版本及以後的系統。在某些發行版本中,如SuSE 8.2, 有讓2.4版本的核心支援epoll的補丁。
* rtsig – 可執行的實時訊號,使用於Linux核心版本2.2.19以後的系統。預設情況下整個系統中不能出現大於1024個POSIX實時(排隊)訊號。這種情況對於高負載的伺服器來說是低效的;所以有必要通過調節核心引數 /proc/sys/kernel/rtsig-max 來增加佇列的大小。
可是從Linux核心版本2.6.6-mm2開始, 這個引數就不再使用了,並且對於每個程序有一個獨立的訊號佇列,這個佇列的大小可以用 RLIMIT_SIGPENDING 引數調節。當這個佇列過於擁塞,nginx就放棄它並且開始使用 poll 方法來處理連線直到恢復正常。
* /dev/poll – 高效的方法,使用於 Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+ 和 Tru64 UNIX 5.1A+.
* eventport – 高效的方法,使用於 Solaris 10. 為了防止出現核心崩潰的問題, 有必要安裝這個安全補丁。
在linux下面,只有epoll是高效的方法,下面再來看看epoll到底是如何高效的。
Epoll是Linux核心為處理大批量控制代碼而作了改進的poll。要使用epoll只需要這三個系統呼叫:epoll_create(2), epoll_ctl(2), epoll_wait(2)。它是在2.5.44核心中被引進的(epoll(4) is a new API introduced in Linux kernel 2.5.44),在2.6核心中得到廣泛應用。
epoll的優點
* 支援一個程序開啟大數目的socket描述符(FD)
select最不能忍受的是一個程序所開啟的FD是有一定限制的,由FD_SETSIZE設定,預設值是2048。對於那些需要支援的上萬連線數目的IM伺服器來說顯然太少了。這時候你一是可以選擇修改這個巨集然後重新編譯核心,不過資料也同時指出這樣會帶來網路效率的下降。
二是可以選擇多程序的解決方案(傳統的 Apache方案),不過雖然linux上面建立程序的代價比較小,但仍舊是不可忽視的,加上程序間資料同步遠比不上執行緒間同步的高效,所以也不是一種完美的方案。
不過 epoll則沒有這個限制,它所支援的FD上限是最大可以開啟檔案的數目,這個數字一般遠大於2048,舉個例子,在1GB記憶體的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關係很大。
* IO效率不隨FD數目增加而線性下降
傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由於網路延時,任一時間只有部分的socket是”活躍”的,但是select/poll每次呼叫都會線性掃描全部的集合,導致效率呈現線性下降。但是epoll不存在這個問題,它只會對”活躍”的socket進行操作—這是因為在核心實現中epoll是根據每個fd上面的callback函式實現的。
那麼,只有”活躍”的socket才會主動的去呼叫 callback函式,其他idle狀態socket則不會,在這點上,epoll實現了一個”偽”AIO,因為這時候推動力在os核心。在一些 benchmark中,如果所有的socket基本上都是活躍的—比如一個高速LAN環境,epoll並不比select/poll有什麼效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。
* 使用mmap加速核心與使用者空間的訊息傳遞。
這點實際上涉及到epoll的具體實現了。無論是select,poll還是epoll都需要核心把FD訊息通知給使用者空間,如何避免不必要的記憶體拷貝就很重要,在這點上,epoll是通過核心於使用者空間mmap同一塊記憶體實現的。而如果你想我一樣從2.5核心就關注epoll的話,一定不會忘記手工 mmap這一步的。
* 核心微調
這一點其實不算epoll的優點了,而是整個linux平臺的優點。也許你可以懷疑linux平臺,但是你無法迴避linux平臺賦予你微調核心的能力。
比如,核心TCP/IP協議棧使用記憶體池管理sk_buff結構,那麼可以在執行時期動態調整這個記憶體pool(skb_head_pool)的大小— 通過echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函式的第2個引數(TCP完成3次握手的資料包佇列長度),也可以根據你平臺記憶體大小動態調整。
更甚至在一個數據包面數目巨大但同時每個資料包本身大小卻很小的特殊系統上嘗試最新的NAPI網絡卡驅動架構。