Java IO:BIO和NIO差別及各自應用場景


轉載請注明出處:jiq•欽's technical Blog - 季義欽


引言

BIO和NIO是兩種不同的網絡通信模型,現現在NIO已經大量應用在Jetty、ZooKeeper、Netty等開源框架中。


一個面向流、一個面向緩沖區

一個是堵塞式的、一個非堵塞

一個沒有io多路復用器、一個有

以下通過一個樣例解釋兩者差別:

假設當前服務端程序須要同一時候從與多個client建立的連接讀取數據。

 

使用BIO

假設採用堵塞式IO。單線程情況下。處理者線程可能堵塞在當中一個套接字的read上,導致還有一個套接字即使准備好了數據也無法處理。這個時候解決辦法就是針對每個套接字。都新建一個線程處理其數據讀取。

所以說,在BIO工作模式下,服務端程序要想同一時候處理多個套接字的數據讀取,在等待接收連接請求的主線程之外,還要為每個建立好的連接分配一個新的線程進行處理。

 

使用NIO

輪詢方式

假設將套接字讀操作換成非堵塞的,那么僅僅須要一個線程就能夠同一時候處理套接字,每次檢查一個套接字。有數據則讀取,沒有則檢查下一個。由於是非堵塞的。所以運行read操作時若沒有數據准備好則馬上返回。不會發生堵塞。

 

I/O多路復用

這樣的輪詢的方式缺點是浪費CPU資源。大部分時間可能都是無數據可讀的,不必仍不間斷的重復運行read操作,I/O多路復用(IOmultiplexing)是一種更好的方法。調用select函數時。其內部會維護一張監聽的套接字的列表,其會一直堵塞直到當中某一個套接字有數據准備好才返回。並告訴是哪個套接字可讀,這時再調用該套接字的read函數效率更高。


所以基本能夠覺得 “NIO = I/O多路復用 + 非堵塞式I/O”,大部分情況下是單線程。但也有超過一個線程實現NIO的情況

 

NIO三種模型

上面所講到的僅僅須要一個線程就能夠同一時候處理多個套接字,這僅僅是當中的一種單線程模型,是一種較為極端的情況。NIO主要包括三種線程模型:

1) Reactor單線程模型

2) Reactor多線程模型

3)主從Reactor多線程模型

 

Reactor單線程模型:

單個線程完畢全部事情包括接收client的TCP連接請求,讀取和寫入套接字數據等。

對於一些小容量應用場景。能夠使用單線程模型。可是對於高負載、大並發的應用卻不合適。主要原因例如以下:

1) 一個NIO線程同一時候處理成百上千的鏈路,性能上無法支撐,即便NIO線程的CPU負荷達到100%,也無法滿足海量消息的編碼、解碼、讀取和發送;

2) 當NIO線程負載過重之后。處理速度將變慢,這會導致大量client連接超時,超時之后往往會進行重發。這更加重了NIO線程的負載,終於會導致大量消息積壓和處理超時,NIO線程會成為系統的性能瓶頸;

3) 可靠性問題:一旦NIO線程意外跑飛,或者進入死循環,會導致整個系統通信模塊不可用。不能接收和處理外部消息,造成節點故障。

為了解決這些問題。演進出了Reactor多線程模型。

 

Reactor多線程模型:

Rector多線程模型與單線程模型最大的差別就是有一組NIO線程處理真實的IO操作。

Reactor多線程模型的特點:

1) 有專門一個NIO線程-Acceptor線程用於監聽服務端,接收client的TCP連接請求;

2) 網絡IO操作-讀、寫等由一個NIO線程池負責,線程池能夠採用標准的JDK線程池實現。它包括一個任務隊列和N個可用的線程,由這些NIO線程負責消息的讀取、解碼、編碼和發送;

3) 1個NIO線程能夠同一時候處理N條鏈路。可是1個鏈路僅僅相應1個NIO線程,防止發生並發操作問題。

在絕大多數場景下,Reactor多線程模型都能夠滿足性能需求;可是,在極特殊應用場景中。一個NIO線程負責監聽和處理全部的client連接可能會存在性能問題

比如百萬client並發連接,或者服務端須要對client的握手消息進行安全認證,認證本身很損耗性能。在這類場景下,單獨一個Acceptor線程可能會存在性能不足問題,為了解決性能問題。產生了第三種Reactor線程模型-主從Reactor多線程模型。

即從單線程中由一個線程即監聽連接事件、讀寫事件、由完畢數據讀寫,拆分為由一個線程專門監聽各種事件。再由專門的線程池負責處理真正的IO數據讀寫。

 

主從Reactor多線程模型

主從Reactor線程模型與Reactor多線程模型的最大差別就是有一組NIO線程處理連接、讀寫事件。

主從Reactor線程模型的特點是:服務端用於接收client連接的不再是個1個單獨的NIO線程。而是一個獨立的NIO線程池。Acceptor接收到clientTCP連接請求處理完畢后(可能包括接入認證等),將新創建的SocketChannel注冊到IO線程池(sub reactor線程池)的某個IO線程上,由它負責SocketChannel的讀寫和編解碼工作。

Acceptor線程池僅僅僅僅用於client的登陸、握手和安全認證,一旦鏈路建立成功,就將鏈路注冊到后端subReactor線程池的IO線程上,由IO線程負責興許的IO操作。

即從多線程模型中由一個線程來監聽連接事件和數據讀寫事件,拆分為一個線程監聽連接事件,線程池的多個線程監聽已經建立連接的套接字的數據讀寫事件,另外和多線程模型一樣有專門的線程池處理真正的IO操作。


各自適用場景

NIO適用場景

server須要支持超大量長時間連接。比方10000個連接以上,而且每個client並不會頻繁地發送太多數據。比如總公司的一個中心server須要收集全國便利店各個收銀機的交易信息。僅僅須要少量線程按需處理維護的大量長期連接。

Jetty、Mina、Netty、ZooKeeper等都是基於NIO方式實現。

 

BIO適用場景

適用於連接數目比較小。而且一次發送大量數據的場景,這樣的方式對server資源要求比較高,並發局限於應用中。

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com