BIO、NIO、AIO的理解

一、BIO的理解

首先我們通過通信模型圖來熟悉下BIO的服務(wù)端通信模型:采用BIO通信模型的服務(wù)端,通常由一個(gè)獨(dú)立的Acceptor線程負(fù)責(zé)監(jiān)聽客戶端的連接,它接收到客戶端的連接請求之后為每個(gè)客戶端創(chuàng)建一個(gè)新的線程進(jìn)行鏈路處理,處理完成之后,通過輸出流返回應(yīng)答給客戶端,線程銷毀。這就是典型的一請求一應(yīng)答通信模型。這個(gè)是在多線程情況下執(zhí)行的。當(dāng)在單線程環(huán)境下時(shí),在while循環(huán)中服務(wù)端會調(diào)用accept方法等待接收客戶端的連接請求,一旦接收到一個(gè)連接請求,就可以建立socket,并在該socket上進(jìn)行讀寫操作,此時(shí)不能再接收其它客戶端的連接請求,只能等待同當(dāng)前連接的客戶端的操作執(zhí)行完成。
該模型最大的問題就是缺乏彈性伸縮能力,當(dāng)客戶端并發(fā)訪問量增加后,服務(wù)端的線程個(gè)數(shù)和客戶端并發(fā)訪問數(shù)呈1:1的正比關(guān)系,由于線程是Java虛擬機(jī)非常寶貴的系統(tǒng)資源,當(dāng)線程數(shù)膨脹之后,系統(tǒng)的性能將急劇下降,隨著并發(fā)訪問量的繼續(xù)增大,系統(tǒng)會發(fā)生線程堆棧溢出、創(chuàng)建新線程失敗等問題,并最終導(dǎo)致進(jìn)程宕機(jī)或者僵死,不能對外提供服務(wù)。

二、偽異步I/O編程

為了解決同步阻塞I/O面臨的一個(gè)鏈路需要一個(gè)線程處理的問題,后來有人對它的線程模型進(jìn)行了優(yōu)化,后端通過一個(gè)線程池來處理多個(gè)客戶端的請求接入,形成客戶端個(gè)數(shù)M:線程池最大線程數(shù)N的比例關(guān)系,其中M可以遠(yuǎn)遠(yuǎn)大于N,通過線程池可以靈活的調(diào)配線程資源。設(shè)置線程的最大值,防止由于海量并發(fā)接入導(dǎo)致線程耗盡。
當(dāng)有新的客戶端接入時(shí),將客戶端的Socket封裝成一個(gè)Task(該任務(wù)實(shí)現(xiàn)Java.lang.Runnablle接口)投遞到后端的線程池中進(jìn)行處理,JDK的線程池維護(hù)一個(gè)消息隊(duì)列和N個(gè)活躍線程對消息隊(duì)列中的任務(wù)進(jìn)行處理。由于線程池可以設(shè)置消息隊(duì)列的大小和最大線程數(shù),因此,它的資源占用是可控的,無論多少個(gè)客戶端并發(fā)訪問,都不會導(dǎo)致資源的耗盡和宕機(jī)。
由于線程池和消息隊(duì)列都是有界的,因此,無論客戶端并發(fā)連接數(shù)多大,它都不會導(dǎo)致線程個(gè)數(shù)過于膨脹或者內(nèi)存溢出,相對于傳統(tǒng)的一連接一線程模型,是一種改良。
偽異步I/O通信框架采用了線程池實(shí)現(xiàn),因此避免了為每個(gè)請求都創(chuàng)建一個(gè)獨(dú)立線程造成的線程資源耗盡問題。但是由于它底層的通信依然采用同步阻塞模型,因此無法從根本上解決問題。
通過對輸入和輸出流的API文檔進(jìn)行分析,我們了解到讀和寫操作都是同步阻塞的,阻塞的時(shí)間取決于對方IO線程的處理速度和網(wǎng)絡(luò)IO的傳輸速度,本質(zhì)上講,我們無法保證生產(chǎn)環(huán)境的網(wǎng)絡(luò)狀況和對端的應(yīng)用程序能足夠快,如果我們的應(yīng)用程序依賴對方的處理速度,它的可靠性就會非常差。

三、NIO編程(非阻塞IO)

與Socket類和ServerSocket類相對應(yīng),NIO也提供了SocketChannel和ServerSocketChannel兩種不同的套接字通道實(shí)現(xiàn),在JDK1.4中引入。這兩種新增的通道都支持阻塞和非阻塞兩種模式。阻塞模式使用非常簡單,但是性能和可靠性都不好,非阻塞模式則正好相反。我們可以根據(jù)自己的需求來選擇合適的模式,一般來說,低負(fù)載、低并發(fā)的應(yīng)用程序可以選擇同步阻塞IO以降低編程復(fù)雜度,但是對于高負(fù)載、高并發(fā)的網(wǎng)絡(luò)應(yīng)用,需要使用NIO的非阻塞模式進(jìn)行開發(fā)。
首先來了解一些概念
(1)緩沖區(qū)Buffer
Buffer是一個(gè)對象,它包含一些要寫入或者要讀出的數(shù)據(jù),在NIO庫中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在讀取數(shù)據(jù)時(shí),它是直接讀到緩沖區(qū)中的;在寫入數(shù)據(jù)時(shí),寫入到緩沖區(qū)中,任何時(shí)候訪問NIO中的數(shù)據(jù),都是通過緩沖區(qū)進(jìn)行操作。
緩沖區(qū)實(shí)質(zhì)上是一個(gè)數(shù)組。通常它是一個(gè)字節(jié)數(shù)組(ByteBuffer),也可以使用其他種類的數(shù)組,但是一個(gè)緩沖區(qū)不僅僅是一個(gè)數(shù)組,緩沖區(qū)提供了對數(shù)據(jù)的結(jié)構(gòu)化訪問以及維護(hù)讀寫位置(limit)等信息。常用的有ByteBuffer,其它還有CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
(2)通道Channel
Channel是一個(gè)通道,可以通過它讀取和寫入數(shù)據(jù),它就像自來水管一樣,網(wǎng)絡(luò)數(shù)據(jù)通過Channel讀取和寫入。通道與流的不同之處在于通道是雙向的,流只是一個(gè)方向上移動(一個(gè)流必須是InputStream或者OutputStream的子類),而且通道可以用于讀、寫或者用于讀寫。同時(shí)Channel是全雙工的,因此它可以比流更好的映射底層操作系統(tǒng)的API。特別是在Unix網(wǎng)絡(luò)編程中,底層操作系統(tǒng)的通道都是全雙工的,同時(shí)支持讀寫操作。我們常用到的ServerSocketChannnel和SocketChannel都是SelectableChannel的子類。
(3)多路復(fù)用器Selector
多路復(fù)用器Selector是Java NIO編程的基礎(chǔ),多路復(fù)用器提供選擇已經(jīng)就緒的任務(wù)的能力,簡單的說,Selector會不斷的輪詢注冊在其上的Channel,如果某個(gè)Channel上面有新的TCP連接接入、讀和寫事件,這個(gè)Channel就處于就緒狀態(tài),會被Selector輪詢出來,然后通過SelectionKey可以獲取就緒Channel的集合,進(jìn)行后續(xù)的I/O操作。
一個(gè)多用復(fù)用器Selector可以同時(shí)輪詢多個(gè)Channel,由于JDK使用了epoll()代替?zhèn)鹘y(tǒng)的select實(shí)現(xiàn),所以它并沒有最大連接句柄1024/2048的限制,這也意味著只需要一個(gè)線程負(fù)責(zé)Selector的輪詢,就可以接入成千上萬的客戶端。
管NIO編程難度確實(shí)比同步阻塞BIO大很多,但是我們要考慮到它的優(yōu)點(diǎn):
(1)客戶端發(fā)起的連接操作是異步的,可以通過在多路復(fù)用器注冊O(shè)P_CONNECT等后續(xù)結(jié)果,不需要像之前的客戶端那樣被同步阻塞。
(2)SocketChannel的讀寫操作都是異步的,如果沒有可讀寫的數(shù)據(jù)它不會同步等待,直接返回,這樣IO通信線程就可以處理其它的鏈路,不需要同步等待這個(gè)鏈路可用。
(3)線程模型的優(yōu)化:由于JDK的Selector在Linux等主流操作系統(tǒng)上通過epoll實(shí)現(xiàn),它沒有連接句柄數(shù)的限制(只受限于操作系統(tǒng)的最大句柄數(shù)或者對單個(gè)進(jìn)程的句柄限制),這意味著一個(gè)Selector線程可以同時(shí)處理成千上萬個(gè)客戶端連接,而且性能不會隨著客戶端的增加而線性下降,因此,它非常適合做高性能、高負(fù)載的網(wǎng)絡(luò)服務(wù)器。

四、AIO(異步非阻塞IO)

JDK1.7升級了NIO類庫,升級后的NIO類庫被稱為NIO2.0。也就是我們要介紹的AIO。NIO2.0引入了新的異步通道的概念,并提供了異步文件通道和異步套接字通道的實(shí)現(xiàn)。異步通道提供兩種方式獲取操作結(jié)果。
(1)通過Java.util.concurrent.Future類來表示異步操作的結(jié)果;
(2)在執(zhí)行異步操作的時(shí)候傳入一個(gè)Java.nio.channels.
CompletionHandler接口的實(shí)現(xiàn)類作為操作完成的回調(diào)。
NIO2.0的異步套接字通道是真正的異步非阻塞IO,它對應(yīng)UNIX網(wǎng)絡(luò)編程中的事件驅(qū)動IO(AIO),它不需要通過多路復(fù)用器(Selector)對注冊的通道進(jìn)行輪詢操作即可實(shí)現(xiàn)異步讀寫,從而簡化了NIO的編程模型。
我們可以得出結(jié)論:異步Socket Channel是被動執(zhí)行對象,我們不需要想NIO編程那樣創(chuàng)建一個(gè)獨(dú)立的IO線程來處理讀寫操作。對于AsynchronousServerSocketChannel和AsynchronousSocketChannel,它們都由JDK底層的線程池負(fù)責(zé)回調(diào)并驅(qū)動讀寫操作。正因?yàn)槿绱?,基于NIO2.0新的異步非阻塞Channel進(jìn)行編程比NIO編程更為簡單。

總結(jié)

總結(jié)

由上述總結(jié)得出,并不意味著所有的Java網(wǎng)絡(luò)編程都必須要選擇NIO和Netty,具體選擇什么樣的IO模型或者NIO框架,完全基于業(yè)務(wù)的實(shí)際應(yīng)用場景和性能訴求,如果客戶端并發(fā)連接數(shù)不多,周邊對接的網(wǎng)元不多,服務(wù)器的負(fù)載也不重,那就完全沒必要選擇NIO做服務(wù)端;如果是相反情況,那就考慮選擇合適的NIO框架進(jìn)行開發(fā)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容