為什麼Nginx的性能要比Apache高得多?
這主要是因為Nginx使用了最新的epoll(Linux 2.6核心)和kqueue(FreeBSD)網絡I/O模型,而Apache則使用的是傳統的select模型。曾在一篇部落格上看到有這麼個執行個體:
假設你在大學中讀書,要等待一個朋友來訪,而這個朋友隻知道你在A号樓,但是不知道你具體住在哪裡,于是你們約好了在A号樓門口見面.如果你使用的阻塞IO 模型來處理這個問題,那麼你就隻能一直守候在A号樓門口等待朋友的到來,在這段時間裡你不能做别的事情,不難知道,這種方式的效率是低下的.現在時代變化了,開始使用多路複用IO模型來處理這個問題.你告訴你的朋友來了A号樓找樓管大媽,讓她告訴你該怎麼走.這裡的樓管大媽扮演的就是多路複用IO的角色。
解釋select和epoll模型的工作方式:
select版大媽做的是如下的事情:比如同學甲的朋友來了,select版大媽比較笨,她帶着朋友挨個房間進行查詢誰是同學甲,你等的朋友來了。如果每到來一個朋友樓管大媽都要全樓的查詢同學,那麼處理的效率必然就低下了,過不久樓底就有不少的人了。
epoll版大媽就比較先進了,她記下了同學甲的資訊,比如說他的房間号,那麼等同學甲的朋友到來時,隻需要告訴該朋友同學甲在哪個房間即可,不用自己親自帶着人滿大樓的找人了。epoll大媽可以不用吹灰之力就可以定位到同學甲。一看就很明白 epoll和select 模型的差別了吧。
在Linux核心中,select所用到的FD_SET是有限的,即核心中有個參數__FD_SETSIZE定義了每個FD_SET的句柄個數,在核心源碼中 /usr/include/linux/posix_types.h 中
#undef __FD_SETSIZE
#define __FD_SETSIZE 1024
如果想要同時檢測1025個句柄的可讀狀态或 可寫狀态 ,select是不能實作的。在核心中實作select是使用輪詢方法,即每次檢測都會周遊所有FD_SET中的句柄,顯然,select函數的執行時間與 FD檢測的句柄數越多就會越費時。
epoll是多路複用IO(I/O Multiplexing) 中的一種方式,僅用于linux2.6以上核心。而epoll模型它所支援的FD上限是最大可以打開檔案的數目,這個數字一般遠大于2048,舉個例子,在1GB記憶體的機器上大約是10萬左右,具體請檢視:cat /proc/sys/fs/file-max ,這個數目和系統記憶體關系很大。
傳統的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之上了。
epoll有兩種工作模式:Edge Triggered (ET)、Level Triggered (LT)
LT(level triggered)是預設的工作方式,并且同時支援block和no-block socket.在這種做法中,核心告訴你一個檔案描述符是否就緒了,然後可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你的,是以,這種模式程式設計出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。