本文是Netty系列筆記第2篇web
Netty是網絡應用框架,因此從最本質的角度來看,是對網絡I/O模型的封裝使用。數組
所以,要深入理解Netty的高性能,也必須從網絡I/O模型提及。服務器
看完本文,能夠回答這三個問題:微信
五種I/O模型是什麼?核心區別在哪裏?網絡
同步=阻塞?異步=非阻塞?多線程
Netty的高性能,是採用了哪一種I/O模型?併發
1.掌握五種I/O模型的關鍵鑰匙
Unix系統下的五種基本I/O模型你們應該都有所耳聞,分爲:app
blocking I/O(同步阻塞IO,BIO)框架
nonblocking I/O(同步非阻塞IO,NIO)異步
I/O multiplexing (I/O多路複用)
signal driven I/O(信號驅動I/O)
asynchronous I/O(異步I/O,AIO)
每種I/O的特性如何,尤爲是同步/非同步、阻塞/非阻塞的區別,其實不少人並不能準確地進行區分。
因此,咱們先把最核心的「鑰匙」告訴你們,帶着這把「鑰匙」再來看I/O模型的關鍵問題,就能手到擒來了。
當一次網絡IO發生時,主要涉及到三個對象:
發起這次IO操做的Process或者Application
系統內核kernel。用戶進程沒法直接操做I/O設備,必須經過系統內核kernel與I/O設備交互。
I/O設備,包括網絡、磁盤等。本文主要針對網絡。
真正的I/O過程,主要分爲兩個階段:
等待數據準備階段。
數據拷貝階段。數據準備完畢,從內核kernel拷貝到進程process中
以一個socket上的輸入操做爲例。
第一步一般涉及等待數據從網絡中到達。當所等待分組到達時,它被複制到內核中的某個緩衝區。
第二步就是把數據從內核緩衝區複製到用戶態緩衝區。
這裏,咱們先記住這 兩個階段,掌握全部I/O模型區別的「關鍵鑰匙」就在它們身上。
2.五種I/O模型詳解
2.1 同步阻塞I/O, BIO
咱們通常使用最多的,最基礎的I/O模型就是同步阻塞I/O。
典型應用:
阻塞socket、Java BIO
咱們來解讀一下BIO的過程:
應用進程向內核發起 I/O 請求,發起調用的線程 一直阻塞,等待內核返回結果。
數據準備完畢,從內核kernel拷貝到用戶態內存(仍舊阻塞),而後kernel返回結果,用戶進程process結束阻塞,從新運行。
「關鍵鑰匙」分析:
BIO的特色就是在IO執行的 兩個階段 都被 阻塞 了。
因此,咱們平常使用BIO模型的時候,提升性能的方式,就是採用 多線程。
在通常的場景中,多線程模型下的BIO是成本較低、收益較高的方式。可是,若是在高併發的場景下,過多的建立線程,會嚴重佔據系統資源,下降系統對外界響應效率。
那是否是能夠考慮使用「線程池」或者「鏈接池」呢?
必定程度上能夠。「池化」的目的在於減小建立和銷燬線程的頻率,讓空閒的線程從新承擔新的執行任務,維持一個合理的線程數量,能夠很好的下降系統開銷。
可是,「池化」技術只能必定程度上緩解了頻繁調用IO接口帶來的資源佔用。若是「池」上限100,而咱們須要1000的IO,那並不能解決性能問題,這是因爲BIO模型自己的限制決定的。
因此,須要非阻塞I/O來嘗試解決這個問題。
2.2 同步非阻塞I/O, NIO
BIO的阻塞問題,讓咱們考慮使用非阻塞的NIO模型。
典型應用:
socket的非阻塞模式
應用進程向內核發起 I/O 請求後,若是kernel中的數據尚未準備好,再也不會「阻塞」等待結果,而是會當即返回。
從用戶進程角度講 ,它發起一個IO操做後,並不須要等待,而是立刻就獲得了一個結果。
用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而它開始發起輪訓操做。
直到kernel中的數據準備好了,一旦用戶再輪訓過來,就立刻將數據拷貝到了用戶內存,而後返回。
因此,在非阻塞式IO中,用戶進程實際上是須要不斷地主動詢問kernel數據準備好了沒有。
「關鍵鑰匙」分析:
非阻塞NIO模型相比於BIO的顯著差別在於,在「數據等待」階段,再也不「阻塞」,當即返回。
可是在「數據拷貝」階段,仍然是「阻塞」的。
雖然非阻塞模型避免了「數據等待」階段的阻塞,可是,採用輪詢方式,會致使系統上下文切換開銷很大,會大幅度推高CPU 佔用率。
所以,單獨使用非阻塞 I/O 模型的效率並不高。並且隨着併發量的提高,非阻塞 I/O 會存在嚴重的性能浪費。
咱們能夠看到,輪訓的目的只是檢測「數據是否已經就緒」,而操做系統提供了更爲高效的檢測接口,
例如select()多路複用模式,能夠一次檢測多個鏈接是否活躍。
2.3 多路複用IO
多路複用實現了一個線程處理多個 I/O 句柄的操做,有些地方也稱這種IO方式爲事件驅動IO(event driven IO)。
多路 指的是多個數據通道
複用 指的是使用一個或多個固定線程來處理每個 Socket。
典型應用:
select、poll、epoll三種方案
Java NIO
多個的進程的IO能夠註冊到一個複用器(selector)上,而後用一個進程調用select,select會監聽全部註冊進來的IO。
若是selector全部監聽的IO在內核緩衝區都沒有可讀數據,select調用進程會被阻塞;同時,kernel會「監視」全部select負責的socket,若是任何一個socket中的數據準備好了,select就會返回;
而後select調用進程能夠本身或通知另外的進程(註冊進程)來再次發起讀取IO,而後process將數據從kernel拷貝到用戶進程,讀取內核中準備好的數據。
能夠看到,多個進程註冊IO後,只有一個select調用進程被阻塞。
多路複用解決了同步阻塞 I/O 和同步非阻塞 I/O 的問題,是一種很是高效的 I/O 模型。咱們能夠直觀看到,這個模型的好處在於單個process就能夠同時處理多個網絡鏈接的IO。
「關鍵鑰匙」分析:
多路複用I/O,select階段,對於多路socket的「數據等待」階段而言,是「非阻塞」。
對單個socket的「數據拷貝」階段,也是「阻塞」。
這裏須要特別注意!!!!
其實若是處理的IO數很少的狀況下,使用多路複用IO的web server不必定比使用 池化+BIO 的web server性能更好,可能延遲還更大。
考慮極端狀況下,只有一個IO,多路複用須要 2 次系統調用(select + recvfrom),而BIO只須要 1 次系統調用(recvfrom)。
因此,多路複用IO的優點並非對於單個鏈接能處理得更快,而是在於能處理更多的鏈接。
2.4 信號驅動I/O
在使用信號驅動 I/O 時,當數據準備就緒後,內核經過發送一個 SIGIO 信號通知應用進程,應用進程就能夠開始讀取數據了。
信號驅動I/O模型的最大特色,就是不須要process進程不斷輪訓內核是否已經準備就緒。
「關鍵鑰匙」分析:
信號驅動I/O在"數據等待"階段「非阻塞」。
當數據準備完成後,信號通知process,process開始「數據拷貝」階段,這裏仍然是「阻塞」的。
信號驅動 I/O 有幾個缺陷:
1)在大量 IO 操做時可能會由於信號隊列溢出致使無法通知。
2)信號驅動 I/O 儘管對於處理 UDP 套接字來講有用,信號通知意味着到達一個數據報,或者返回一個異步錯誤。
可是,對於 TCP 而言,信號驅動的 I/O 方式不太好用。由於致使信號通知的狀況有很是多種,每個來進行判別會消耗很大資源。
因此信號驅動I/O模式用得很是少。
並且尤爲須要注意,在「數據拷貝」階段,它仍然是「阻塞」的。
2.5 異步I/O,AIO
真正的異步I/O,就是AIO。
典型應用:
JAVA7 AIO、高性能服務器
根據前面四個模型的分析,相信你們已經能明顯看懂這個模型的運行方式了。
用戶進程發起I/O請求後,馬上就能夠開始去作其它的事。而另外一方面,從kernel的角度,當它收到一個請求以後,首先它會馬上返回,因此不會對用戶進程產生任何block。而後,kernel會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,kernel會給用戶進程發送一個signal,告訴它I/O操做完成了。
AIO最重要的一點是 從內核緩衝區拷貝數據到用戶態緩衝區的過程也是由系統異步完成,應用進程只須要在指定的數組中引用數據便可。
AIO 與信號驅動 I/O 的主要區別:
信號驅動 I/O 由內核通知什麼時候能夠開始一個 I/O 操做,而異步 I/O 由內核通知 I/O 操做什麼時候已經完成。
「關鍵鑰匙」分析:
"數據等待"階段,非阻塞
"數據拷貝」階段,非阻塞
AIO是真正的異步模型,它不會對請求進程產生任何的阻塞。
3. 同步=阻塞?異步=非阻塞?
平常使用過程當中,咱們每每把 同步I/O 等同於 阻塞I/O,異步I/O 等同於 非阻塞I/O。
實際上,嚴格意義來講,這兩組概念仍是有很大的區別的。
3.1 阻塞I/O 與 非阻塞I/O
阻塞與非阻塞的區別比較明顯,也很好理解。
結合I/O模型來講,阻塞I/O會一直block對應的進程直到操做完成,而非阻塞 IO在kernel 在"等待數據準備"階段會馬上返回。
因此咱們通常認爲,阻塞I/O只有BIO,另外四個模型都是屬於非阻塞I/O。
3.2 同步I/O 與 異步I/O
先來看看 同步I/O 和 異步I/O 的定義是什麼,根據POSIX的定義:
同步I/O : A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
異步I/O : An asynchronous I/O operation does not cause the requesting process to be blocked;
二者的區別就在於同步I/O作 "IO operation」的時候會將process阻塞。
那麼按照這個定義,咱們看看前面每一個模型的「關鍵鑰匙」分析部分,能夠明顯看到,BIO,NIO,IO多路複用、信號驅動IO 四種模型都屬於 同步IO。
由於它們在IO的第二階段,真正執行「數據拷貝」的階段,都是「阻塞」的。以NIO爲例,在執行recvfrom這個系統調用的時候,若是kernel的數據沒有準備好,這時候不會block進程。可是當kernel中數據準備好的時候,recvfrom會將數據從kernel拷貝到用戶內存中,這個時候進程是被block了。
同理,信號驅動IO,當內核中IO數據就緒時以SIGIO信號通知請求進程,請求進程再把數據從內核讀入到用戶空間,這一步也是阻塞的。
因此,真正的異步I/O只有一個,就是AIO。當進程發起IO操做以後,就直接返回不再管了,直到kernel發送一個信號,告訴進程說IO完成。在這整個過程當中,進程徹底沒有被阻塞。如定義所說,不會由於IO操做阻塞。
4. Netty採用了哪一種I/O模型呢?
Netty 的 I/O 模型是基於非阻塞 I/O 實現的,底層依賴的是 JDK NIO 框架的多路複用器 Selector。
一個多路複用器 Selector 能夠同時輪詢多個 Channel,採用 epoll 模式後,只須要一個線程負責 Selector 的輪詢,就能夠接入成千上萬的客戶端。
更具體的實現方式和模型,咱們下一期再展開說明。
對了,必定有同窗想問,Netty爲何不採用AIO呢?
由於 AIO 的目的是但願 I/O 線程不阻塞主線程,屬於異步 I/O,由內核通知 I/O 操做什麼時候完成。AIO 適用於鏈接數多的且須要長時間鏈接的場景。
對於AIO來講,目前操做系統支持程度有限且實現起來複雜。
Netty也嘗試過AIO,可是效果不是很理想,最終廢棄了。
參考書目:
《UNIX Network Programming(Volume1,3rd)》
若是有任何疑問或者建議,歡迎 寫留言 或者微信和我聯繫哦~
往期熱門筆記合集推薦:
原創:阿丸筆記(微信公衆號:aone_note),歡迎 分享,轉載請保留出處。
掃描下方二維碼能夠關注我哦~
本文分享自微信公衆號 - 阿丸筆記(aone_note)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。