1.Tomcat總體架構(gòu)
Tomcat有Connector和Container兩大核心組件,Connector組件負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求接入,Connector目前支持BIO、NIO、APR三種模式,后續(xù)文章會(huì)再重點(diǎn)對(duì)比下NIO和APR,Tomcat5以后的版本開始支持NIO了;Container組件實(shí)現(xiàn)了對(duì)servlet的容器管理功能;service服務(wù)將Connector和Container又包了一層,包裝成外部可以獲取的服務(wù);多有service都運(yùn)行在Tomcat這個(gè)大Server服務(wù)上,Server有所有service的實(shí)例,并實(shí)現(xiàn)了LifeCycle接口可以控制所有service的生命周期。
2.Tomcat NIO相關(guān)類
Tomcat的NIO實(shí)現(xiàn)主要是在Connector組件內(nèi),Connector 組件是 Tomcat 中兩個(gè)核心組件之一,它的主要任務(wù)是負(fù)責(zé)接收瀏覽器的發(fā)過(guò)來(lái)的 tcp 連接請(qǐng)求,創(chuàng)建一個(gè) Request 和 Response 對(duì)象分別用于和請(qǐng)求端交換數(shù)據(jù),然后會(huì)產(chǎn)生一個(gè)線程來(lái)處理這個(gè)請(qǐng)求并把產(chǎn)生的 Request 和 Response 對(duì)象傳給處理這個(gè)請(qǐng)求的線程,處理這個(gè)請(qǐng)求的線程就是 Container 組件要做的事了。
整個(gè)Connector組件包含三部分:Http11NioProtocol、Mapper、CoyoteAdapter。Http11NioProtocol包含NioEndpoint和Http11ConnectionHandler,NioEndpoint是Http11NioProtocol中負(fù)責(zé)接收處理socket的主要模塊;Http11ConnectionHandler是連接處理器。NioEndpoint主要是實(shí)現(xiàn)了socket請(qǐng)求監(jiān)聽線程Acceptor、socket NIO poller線程、以及請(qǐng)求處理線程池。
NioEndpoint的內(nèi)部處理流程為:
Acceptor 接收socket線程,這里雖然是基于NIO的connector,但是在接收socket方面還是傳統(tǒng)的serverSocket.accept()方式,獲得SocketChannel對(duì)象,然后封裝在一個(gè)tomcat的實(shí)現(xiàn)類org.apache.tomcat.util.net.NioChannel對(duì)象中。然后將NioChannel對(duì)象封裝在一個(gè)PollerEvent對(duì)象中,并將PollerEvent對(duì)象壓入events queue里。這里是個(gè)典型的生產(chǎn)者-消費(fèi)者模式,Acceptor與Poller線程之間通過(guò)queue通信,Acceptor是events queue的生產(chǎn)者,Poller是events queue的消費(fèi)者。
Poller Poller線程中維護(hù)了一個(gè)Selector對(duì)象,NIO就是基于Selector來(lái)完成邏輯的。在connector中并不止一個(gè)Selector,在socket的讀寫數(shù)據(jù)時(shí),為了控制timeout也有一個(gè)Selector,在后面的BlockSelector中介紹。可以先把Poller線程中維護(hù)的這個(gè)Selector標(biāo)為主Selector。 Poller是NIO實(shí)現(xiàn)的主要線程。首先作為events queue的消費(fèi)者,從queue中取出PollerEvent對(duì)象,然后將此對(duì)象中的channel以O(shè)P_READ事件注冊(cè)到主Selector中,然后主Selector執(zhí)行select操作,遍歷出可以讀數(shù)據(jù)的socket,并從Worker線程池中拿到可用的Worker線程,然后將socket傳遞給Worker。整個(gè)過(guò)程是典型的NIO實(shí)現(xiàn)。
Worker Worker線程拿到Poller傳過(guò)來(lái)的socket后,將socket封裝在SocketProcessor對(duì)象中。然后從Http11ConnectionHandler中取出Http11NioProcessor對(duì)象,從Http11NioProcessor中調(diào)用CoyoteAdapter的邏輯,跟BIO實(shí)現(xiàn)一樣。在Worker線程中,會(huì)完成從socket中讀取http request,解析成HttpServletRequest對(duì)象,分派到相應(yīng)的servlet并完成邏輯,然后將response通過(guò)socket發(fā)回client。在從socket中讀數(shù)據(jù)和往socket中寫數(shù)據(jù)的過(guò)程,并沒(méi)有像典型的非阻塞的NIO的那樣,注冊(cè)O(shè)P_READ或OP_WRITE事件到主Selector,而是直接通過(guò)socket完成讀寫,這時(shí)是阻塞完成的,但是在timeout控制上,使用了NIO的Selector機(jī)制,但是這個(gè)Selector并不是Poller線程維護(hù)的主Selector,而是BlockPoller線程中維護(hù)的Selector,稱之為輔Selector。
NioSelectorPool NioEndpoint對(duì)象中維護(hù)了一個(gè)NioSelecPool對(duì)象,這個(gè)NioSelectorPool中又維護(hù)了一個(gè)BlockPoller線程,這個(gè)線程就是基于輔Selector進(jìn)行NIO的邏輯。以執(zhí)行servlet后,得到response,往socket中寫數(shù)據(jù)為例,最終寫的過(guò)程調(diào)用NioBlockingSelector的write方法。
3.請(qǐng)求處理流程
上面介紹了Tomcat的總體架構(gòu)和涉及到NIO的相關(guān)工作類,下面從一個(gè)網(wǎng)絡(luò)請(qǐng)求到Tomcat處理的過(guò)程直到業(yè)務(wù)servlet處理的過(guò)程,整體上說(shuō)下一個(gè)網(wǎng)絡(luò)請(qǐng)求的處理流程,下面借用網(wǎng)上的一張流程圖,如果圖片作者看到覺(jué)得侵權(quán)請(qǐng)下面留言,馬上刪掉:)
對(duì)于Acceptor監(jiān)聽到的Socket請(qǐng)求,經(jīng)過(guò)NioEndpoint內(nèi)部的NIO 線程模型處理后,會(huì)轉(zhuǎn)變?yōu)镾ocketProcessor在Executor中運(yùn)行,其在Run過(guò)程中會(huì)交給Http11ConnectionHandler處理,Http11ConnectionHandler會(huì)從ConcurrentHashMap<NioChannel,Http11NioProcessor>緩存中獲取相應(yīng)的Http11NioProcessor來(lái)繼續(xù)處理,Http11NioProcessor主要是負(fù)責(zé)解析socket請(qǐng)求Header,解析完成后,會(huì)將Request、Response(這里的請(qǐng)求、響應(yīng)在tomcat中看成是coyote的請(qǐng)求、響應(yīng),意思是還需要CoyoteAdaper處理)交給CoyoteAdaper繼續(xù)處理,CoyoteAdaper這里的工作主要將socket解析的Request、Response轉(zhuǎn)化為HttpServletRequest、HttpServletResponse,而這里的請(qǐng)求響應(yīng)就是最后交給Container去處理。
同時(shí)我們可以看到Acceptor線程會(huì)將接受到的SocketChannel(一個(gè)socket請(qǐng)求)封裝為PollerEvent放到Poller線程中的ConcurrentLinkedQueue<PollerEvent>緩存中,注意到這里的緩存是ConcurrentLinkedQueue是支持并發(fā)的,那么在Poller線程的內(nèi)部,它只需要從這個(gè)緩存中不停地獲取PollerEvent然后處理就可以了。最后Poller線程處理完成后會(huì)封裝成SocketProcessor交給NioEndpoint內(nèi)的線程池Executor去處理。線程池中的Work thread線程在處理SocketProcessor過(guò)程中,會(huì)調(diào)用Http11ConnectionHandler處理,而Http11ConnectionHandler則從ConcurrentHashMap<NioChannel,Http11NioProcessor>緩存中獲取相應(yīng)的Http11NioProcessor來(lái)繼續(xù)處理,這里要注意的ConcurrentHashMap也是支持并發(fā)的。
4.NIO相關(guān)參數(shù)
一個(gè)或多個(gè)Acceptor線程,每個(gè)線程都有自己的Selector,Acceptor只負(fù)責(zé)accept新的連接,一旦連接建立之后就將連接注冊(cè)到其他Worker線程中
多個(gè)Worker線程,有時(shí)候也叫IO線程,就是專門負(fù)責(zé)IO讀寫的。一種實(shí)現(xiàn)方式就是像Netty一樣,每個(gè)Worker線程都有自己的Selector,可以負(fù)責(zé)多個(gè)連接的IO讀寫事件,每個(gè)連接歸屬于某個(gè)線程。另一種方式實(shí)現(xiàn)方式就是有專門的線程負(fù)責(zé)IO事件監(jiān)聽,這些線程有自己的Selector,一旦監(jiān)聽到有IO讀寫事件,并不是像第一種實(shí)現(xiàn)方式那樣(自己去執(zhí)行IO操作),而是將IO操作封裝成一個(gè)Runnable交給Worker線程池來(lái)執(zhí)行,這種情況每個(gè)連接可能會(huì)被多個(gè)線程同時(shí)操作,相比第一種并發(fā)性提高了,但是也可能引來(lái)多線程問(wèn)題,在處理上要更加謹(jǐn)慎些。tomcat的NIO模型就是第二種。
所以一般參數(shù)就是Acceptor線程個(gè)數(shù),Worker線程個(gè)數(shù)。
參考官方文檔https://tomcat.apache.org/tomcat-8.5-doc/config/http.html?spm=5176.100239.blogcont39093.5.Vomyf0
參數(shù)主要有以下幾個(gè):
1)acceptCount
連接在被ServerSocketChannel accept之前就暫存在這個(gè)隊(duì)列中,acceptCount就是這個(gè)隊(duì)列的最大長(zhǎng)度。ServerSocketChannel accept就是從這個(gè)隊(duì)列中不斷取出已經(jīng)建立連接的的請(qǐng)求。所以當(dāng)ServerSocketChannel accept取出不及時(shí)就有可能造成該隊(duì)列積壓,一旦滿了連接就被拒絕了;
2)acceptorThreadCount
Acceptor線程只負(fù)責(zé)從上述隊(duì)列中取出已經(jīng)建立連接的請(qǐng)求。在啟動(dòng)的時(shí)候使用一個(gè)ServerSocketChannel監(jiān)聽一個(gè)連接端口如8080,可以有多個(gè)Acceptor線程并發(fā)不斷調(diào)用上述ServerSocketChannel的accept方法來(lái)獲取新的連接。參數(shù)acceptorThreadCount其實(shí)使用的Acceptor線程的個(gè)數(shù);
- maxConnections
這里就是tomcat對(duì)于連接數(shù)的一個(gè)控制,即最大連接數(shù)限制。一旦發(fā)現(xiàn)當(dāng)前連接數(shù)已經(jīng)超過(guò)了一定的數(shù)量(NIO默認(rèn)是10000),上述的Acceptor線程就被阻塞了,即不再執(zhí)行ServerSocketChannel的accept方法從隊(duì)列中獲取已經(jīng)建立的連接。但是它并不阻止新的連接的建立,新的連接的建立過(guò)程不是Acceptor控制的,Acceptor僅僅是從隊(duì)列中獲取新建立的連接。所以當(dāng)連接數(shù)已經(jīng)超過(guò)maxConnections后,仍然是可以建立新的連接的,存放在上述acceptCount大小的隊(duì)列中,這個(gè)隊(duì)列里面的連接沒(méi)有被Acceptor獲取,就處于連接建立了但是不被處理的狀態(tài)。當(dāng)連接數(shù)低于maxConnections之后,Acceptor線程就不再阻塞,繼續(xù)調(diào)用ServerSocketChannel的accept方法從acceptCount大小的隊(duì)列中繼續(xù)獲取新的連接,之后就開始處理這些新的連接的IO事件了; - maxThread
專門處理IO的Worker數(shù),默認(rèn)是200;
這篇文章從tomcat的整體架構(gòu)入手,分別介紹了tomcat中的NIO相關(guān)類,也介紹了一個(gè)網(wǎng)絡(luò)請(qǐng)求在tomcat中的處理流程,最后介紹了一下tomcat中關(guān)鍵的幾個(gè)參數(shù)對(duì)NIO線程模式的作用和影響,相信會(huì)對(duì)希望了解tomcat nio線程模型的同學(xué)會(huì)有所幫助。