1. 程式人生 > >高性能網絡服務器編程:為什麽linux下epoll是最好,Netty要比NIO.2好?

高性能網絡服務器編程:為什麽linux下epoll是最好,Netty要比NIO.2好?

系統 工作效率 lee socket 為我 handler 10g 函數 適合

基本的IO編程過程(包括網絡IO和文件IO)是,打開文件描述符(windows是handler,java是stream或channel),多路捕獲(Multiplexe,即select和poll和epoll)IO可讀寫的狀態,而後可以讀寫的文件描述符進行IO讀寫,由於IO設備速度和CPU內存比速度會慢,為了更好的利用CPU和內存,會開多線程,每個線程讀寫一個文件描述符。
但C10K問題,讓我們意識到在超大數量的網絡連接下,機器設備和網絡速度不再是瓶頸,瓶頸在於操作系統和IO應用程序的溝通協作的方式。
舉個例子,一萬個socket連接過來,傳統的IO編程模型要開萬個線程來應對,還要註意,socket會關閉打開,一萬個線程要不斷的關閉線程重建線程,資源都浪費在這上面了,我們算建立一個線程耗1M內存,1萬個線程機器至少要10G內存,這在IA-32的機器架構下基本是不可能的(要開PAE),現在x64架構才有可能舒服點,要知道,這僅僅是粗略算的內存消耗。別的資源呢?
所以,高性能的網絡編程(即IO編程),第一,需要松綁IO連接和應用程序線程的對應關系,這就是非阻塞(nonblocking)、異步(asynchronous)的要求的由來(構造一個線程池,epoll監控到有數的fd,把fd傳入線程池,由這些worker thread來讀寫io)。第二,需要高性能的OS對IO設備可讀寫(數據來了)的通知方式:從level-triggered notification到edge-triggered notification,關於這個通知方式,我們稍後談。
需要註意異步,不等於AIO(asynchronous IO),linux的AIO和java的AIO都是實現異步的一種方式,都是渣,這個我們也接下來會談到。
針對前面說的這兩點,我們看看select和poll的問題
這兩個函數都在每次調用的時候要求我們把需要監控(看看有沒有數據)的文件描述符,通過數組傳遞進入內核,內核每次都要掃描這些文件描述符,去理解它們,建立一個文件描述符和IO對應的數組(實際內核工作會有好點的實現方式,但可以這麽理解先),以便IO來的時候,通知這些文件描述符,進而通知到進程裏等待的這些select、poll。當有一萬個文件描述符要監控的時候呢(一萬個網絡連接)?這個工作效率是很低的,資源要求卻很高。
我們看epoll
epoll很巧妙,分為三個函數,第一個函數創建一個session類似的東西,第二函數告訴內核維持這個session,並把屬於session內的fd傳給內核,第三個函數epoll_wait是真正的監控多個文件描述符函數,只需要告訴內核,我在等待哪個session,而session內的fd,內核早就分析過了,不再在每次epoll調用的時候分析,這就節省了內核大部分工作。這樣每次調用epoll,內核不再重新掃描fd數組,因為我們維持了session。
說道這裏,只有一個字,開源,贊,眾人拾柴火焰高,贊。
epoll的效率還不僅僅體現在這裏,在內核通知方式上,也改進了,我們先看select和poll的通知方式,也就是level-triggered notification,內核在被DMA中斷,捕獲到IO設備來數據後,本來只需要查找這個數據屬於哪個文件描述符,進而通知線程裏等待的函數即可,但是,select和poll要求內核在通知階段還要繼續再掃描一次剛才所建立的內核fd和io對應的那個數組,因為應用程序可能沒有真正去讀上次通知有數據後的那些fd,應用程序上次沒讀,內核在這次select和poll調用的時候就得繼續通知,這個os和應用程序的溝通方式效率是低下的。只是方便編程而已(可以不去讀那個網絡io,方正下次會繼續通知)。
於是epoll設計了另外一種通知方式:edge-triggered notification,在這個模式下,io設備來了數據,就只通知這些io設備對應的fd,上次通知過的fd不再通知,內核不再掃描一大堆fd了。
基於以上分析,我們可以看到epoll是專門針對大網絡並發連接下的os和應用溝通協作上的一個設計,在linux下編網絡服務器,必然要采用這個,nginx、php的國產異步框架swool、varnish,都是采用這個。
註意還要打開epoll的edge-triggered notification。而java的NIO和NIO.2都只是用了epoll,沒有打開edge-triggered notification,所以不如JBoss的Netty。
接下來我們談談AIO的問題,AIO希望的是,你select,poll,epoll都需要用一個函數去監控一大堆fd,那麽我AIO不需要了,你把fd告訴內核,你應用程序無需等待,內核會通過信號等軟中斷告訴應用程序,數據來了,你直接讀了,所以,用了AIO可以廢棄select,poll,epoll。
但linux的AIO的實現方式是內核和應用共享一片內存區域,應用通過檢測這個內存區域(避免調用nonblocking的read、write函數來測試是否來數據,因為即便調用nonblocking的read和write由於進程要切換用戶態和內核態,仍舊效率不高)來得知fd是否有數據,可是檢測內存區域畢竟不是實時的,你需要在線程裏構造一個監控內存的循環,設置sleep,總的效率不如epoll這樣的實時通知。所以,AIO是渣,適合低並發的IO操作。所以java7引入的NIO.2引入的AIO對高並發的網絡IO設計程序來說,也是渣,只有Netty的epoll+edge-triggered notification最牛,能在linux讓應用和OS取得最高效率的溝通。

高性能網絡服務器編程:為什麽linux下epoll是最好,Netty要比NIO.2好?