前面兩節(jié),我們分別看了BIO和NIO的兩種模式Tomcat的實(shí)現(xiàn)方式。
BIO的方式,就是傳統(tǒng)的一線程,一請(qǐng)求的模式,也就是說(shuō),當(dāng)同時(shí)又1000個(gè)請(qǐng)求過(guò)來(lái),如果Tomcat設(shè)置了最大Accept線程數(shù)為500,那么第一批的500個(gè)線程直接進(jìn)入線程池中進(jìn)行執(zhí)行,而其余500個(gè)根據(jù)Accept的限制的數(shù)量在服務(wù)器端的操作系統(tǒng)的內(nèi)核位置的socket緩沖區(qū)進(jìn)行阻塞,一直到前面500個(gè)線程處理完了之后,Acceptor組件再逐步的放進(jìn)來(lái)。
分析一下,這種模式的BIO的好處,可以讓一個(gè)請(qǐng)求在cpu輪轉(zhuǎn)時(shí)間片切換中最大限度的執(zhí)行,如果業(yè)務(wù)請(qǐng)求不是很長(zhǎng)時(shí)間的事務(wù)處理,通常在一個(gè)時(shí)間片內(nèi)肯定能做完當(dāng)前的請(qǐng)求,這樣的效率算是相當(dāng)?shù)母吡?,因?yàn)槠錅p少了最耗時(shí)也是最頭疼的線程上下文切換;
1.但是,如果事務(wù)執(zhí)行比較長(zhǎng)的時(shí)間,例如等待一個(gè)IO數(shù)據(jù)庫(kù)的操作,那么這個(gè)工作線程就會(huì)根據(jù)cpu輪轉(zhuǎn)不斷的進(jìn)行切換,因?yàn)檎?qǐng)求數(shù)在大并發(fā)中很多,所以不得不設(shè)置一個(gè)很高的Accept線程數(shù),那么從cpu的耗費(fèi)的資源上來(lái)看,甚至有70%的時(shí)間浪費(fèi)在線程切換中,而沒(méi)有真正的時(shí)間去做請(qǐng)求處理和業(yè)務(wù),這是第一個(gè)問(wèn)題。
2.其次,BIO每一次鏈接的建立和釋放都需要重新來(lái)過(guò)一遍,例如一個(gè)socket進(jìn)來(lái)之后,通常會(huì)對(duì)其SocketOptions的屬性進(jìn)行設(shè)置,包括各種Connector中配置都要與其進(jìn)行一一對(duì)應(yīng),加上前面說(shuō)的socket的建立,很多請(qǐng)求通道的資源的初始化都得重新創(chuàng)建,得不到復(fù)用,這個(gè)是第二個(gè)問(wèn)題。
3.最后,BIO方式網(wǎng)絡(luò)IO的阻塞等待是會(huì)讓Accept線程工作效率降低很多的。
所以,基于這3個(gè)問(wèn)題,特別是最后一個(gè)問(wèn)題,引出了NIO的模型。
NIO的架構(gòu)分為三個(gè)線程池,這里再次梳理一下:
1.Acceptor專(zhuān)門(mén)接socket請(qǐng)求,當(dāng)發(fā)現(xiàn)又請(qǐng)求進(jìn)來(lái)后,基于Tomcat配置的SocketOptions和一些屬性的設(shè)置完畢,包裝成SocketChannel,也就是NIO的socket通道抽象,塞入PollerEvent直接扔到隊(duì)列當(dāng)中;
2.Poller線程從隊(duì)列中挨個(gè)獲取PollerEvent,調(diào)用Poller線程自己持有的selector選擇器,注冊(cè)SocketChannel到當(dāng)前的selector選擇器中,然后進(jìn)行selectKey的工作,這樣Acceptor傳遞過(guò)來(lái)的SocketChannel中感興趣的事件,就會(huì)被輪詢出來(lái),當(dāng)接收事件接收之后,需要注冊(cè)O(shè)P_READ事件或者OP_WRITE事件,當(dāng)OP_READ事件或者OP_WRITE事件發(fā)生時(shí),開(kāi)始調(diào)用工作線程池;
3.工作線程池就是SocketProcessor,這個(gè)就是具體的工作線程,SocketProcessor的任務(wù)就是Poller線程從SocketChannel通道中輪詢出來(lái)的數(shù)據(jù)包,進(jìn)行解析,傳遞給后端的handler進(jìn)行http的解析,解析出來(lái)的Request,Reponse對(duì)象,,直接調(diào)用CoyoteAdapter傳遞到后端的容器,通過(guò)Mapper,映射到對(duì)應(yīng)的業(yè)務(wù)Servlet中。可以看到,從SocketProcessor一直到最終的業(yè)務(wù)Servlet實(shí)現(xiàn),這些都是一個(gè)線程,這個(gè)線程就是工作線程。
對(duì)比Tomcat的BIO的架構(gòu),因?yàn)闆](méi)有selector輪詢的操作,所以并沒(méi)有Poller線程,BIO中的Acceptor線程的作用依然是對(duì)socket簡(jiǎn)單的處理和屬性包裝,然后將socket直接扔到工作線程中來(lái)。NIO相當(dāng)于是多了一個(gè)線程池,從流程上來(lái)講,應(yīng)該是多了一道手續(xù),但是通過(guò)NIO本身基于事件觸發(fā)的機(jī)制造成,Acceptor線程沒(méi)必要設(shè)置的過(guò)多,這樣從線程的數(shù)量上來(lái)看,大大的減少線程切換的頻率,其次基于事件進(jìn)行觸發(fā),將Acceptor線程執(zhí)行效率中的網(wǎng)絡(luò)IO延遲降低到最低,大大提升了Acceptor線程的執(zhí)行效率。從這兩點(diǎn)上來(lái)看,Tomcat的NIO在前面分析的BIO的三個(gè)問(wèn)題中第一個(gè)問(wèn)題,和第三個(gè)問(wèn)題都有所改善,特別是第三個(gè)問(wèn)題,全面進(jìn)行了升級(jí)。
但是,對(duì)于BIO中的第一個(gè)問(wèn)題,由后端事務(wù)時(shí)間過(guò)長(zhǎng)導(dǎo)致工作線程池一直在運(yùn)行,并且運(yùn)行在一個(gè)高峰的數(shù)值,不斷的進(jìn)行切換,這種問(wèn)題,NIO通道也沒(méi)辦法進(jìn)行處理,這個(gè)是由業(yè)務(wù)來(lái)決定的,NIO只能保證降低的是Acceptor線程線程數(shù),對(duì)業(yè)務(wù)幫助也是無(wú)能為力的,如果要提升這部分的效率,那就需要應(yīng)用進(jìn)行修改,優(yōu)化JDBC和數(shù)據(jù)庫(kù),或者將業(yè)務(wù)切段來(lái)做,讓事務(wù)時(shí)間盡量控制在一個(gè)可控的范疇之內(nèi)。
對(duì)于第二個(gè)問(wèn)題,無(wú)論是單純的NIO和BIO通道都沒(méi)有辦法進(jìn)行解決,但是HTTP協(xié)議中對(duì)鏈接的復(fù)用進(jìn)行更新,在HTTP1.1中,這個(gè)keepalive是加到http請(qǐng)求頭中的:
Keep-Alive: timeout=5, max=100?
timeout:過(guò)期時(shí)間5秒(對(duì)應(yīng)httpd.conf里的參數(shù)是:KeepAliveTimeout);
max是最多能承受一百次請(qǐng)求的共享復(fù)用,就是在timeout時(shí)間內(nèi)又有新的連接過(guò)來(lái),同時(shí)max會(huì)自動(dòng)減1,直到為0,強(qiáng)制斷掉。?
對(duì)應(yīng)的Tomcat的服務(wù)器端的配置:
keepAliveTimeout:表示在下次請(qǐng)求過(guò)來(lái)之前,tomcat保持該連接多久。這就是說(shuō)假如客戶端不斷有請(qǐng)求過(guò)來(lái),且為超過(guò)過(guò)期時(shí)間,則該連接將一直保持。
maxKeepAliveRequests:表示該連接最大支持的請(qǐng)求數(shù)。超過(guò)該請(qǐng)求數(shù)的連接也將被關(guān)閉(此時(shí)就會(huì)返回一個(gè)Connection: close頭給客戶端)。
如果配置了上述的內(nèi)容,可以解決BIO上面提出的第二個(gè)問(wèn)題,當(dāng)一個(gè)頁(yè)面中的第一個(gè)請(qǐng)求后,后面的連接可以復(fù)用這個(gè)socket或者是socketchannel,不用再accept三次握手或者SSL握手了,相當(dāng)于高效的推動(dòng)了整體Tomcat的時(shí)間鏈條的處理效率,而對(duì)于keepAlive屬性的加入,通過(guò)BIO和NIO對(duì)比測(cè)試發(fā)現(xiàn),相當(dāng)于放大了NIO的優(yōu)勢(shì),導(dǎo)致NIO的測(cè)試結(jié)果要明顯高于BIO一個(gè)水平線上,這也就是目前http1.1協(xié)議中,為什么Tomcat后續(xù)版本默認(rèn)就是NIO的原因;而如果沒(méi)有keepAlive屬性加入,在大多數(shù)的場(chǎng)景下,NIO并沒(méi)有拉開(kāi)與BIO太大的差距,甚至有一些場(chǎng)景上,Tomcat的BIO模式反倒是比NIO要高;
這里單純的對(duì)比性能沒(méi)有任何的意義,因?yàn)樾阅軠y(cè)試是測(cè)試在不同應(yīng)用類(lèi)型,不同硬件環(huán)境,不同軟甲版本,甚至是不同jdk性能差異都很大,客觀因素很多,而且Tomcat的web服務(wù)器目前在企業(yè)應(yīng)用或者是互聯(lián)網(wǎng)應(yīng)用上來(lái)看,都是其鏈條中的微小的時(shí)間占比環(huán)節(jié),甚至有的長(zhǎng)事務(wù)處理鏈條中,Tomcat這塊占比不到1%,當(dāng)然對(duì)于學(xué)習(xí)和研究,更高更快更強(qiáng)是技術(shù)追求的目的,這個(gè)就另當(dāng)別論了。
評(píng)論
查看更多