Tomcat NIO線程模型深入分析

1.Tomcat總體架構


Tomcat有Connector和Container兩大核心組件,Connector組件負責網絡請求接入,Connector目前支持BIO、NIO、APR三種模式,后續文章會再重點對比下NIO和APR,Tomcat5以后的版本開始支持NIO了;Container組件實現了對servlet的容器管理功能;service服務將Connector和Container又包了一層,包裝成外部可以獲取的服務;多有service都運行在Tomcat這個大Server服務上,Server有所有service的實例,并實現了LifeCycle接口可以控制所有service的生命周期。

2.Tomcat NIO相關類

Tomcat的NIO實現主要是在Connector組件內,Connector 組件是 Tomcat 中兩個核心組件之一,它的主要任務是負責接收瀏覽器的發過來的 tcp 連接請求,創建一個 Request 和 Response 對象分別用于和請求端交換數據,然后會產生一個線程來處理這個請求并把產生的 Request 和 Response 對象傳給處理這個請求的線程,處理這個請求的線程就是 Container 組件要做的事了。


整個Connector組件包含三部分:Http11NioProtocol、Mapper、CoyoteAdapter。Http11NioProtocol包含NioEndpoint和Http11ConnectionHandler,NioEndpoint是Http11NioProtocol中負責接收處理socket的主要模塊;Http11ConnectionHandler是連接處理器。NioEndpoint主要是實現了socket請求監聽線程Acceptor、socket NIO poller線程、以及請求處理線程池。
NioEndpoint的內部處理流程為:


Acceptor 接收socket線程,這里雖然是基于NIO的connector,但是在接收socket方面還是傳統的serverSocket.accept()方式,獲得SocketChannel對象,然后封裝在一個tomcat的實現類org.apache.tomcat.util.net.NioChannel對象中。然后將NioChannel對象封裝在一個PollerEvent對象中,并將PollerEvent對象壓入events queue里。這里是個典型的生產者-消費者模式,Acceptor與Poller線程之間通過queue通信,Acceptor是events queue的生產者,Poller是events queue的消費者。
Poller Poller線程中維護了一個Selector對象,NIO就是基于Selector來完成邏輯的。在connector中并不止一個Selector,在socket的讀寫數據時,為了控制timeout也有一個Selector,在后面的BlockSelector中介紹。可以先把Poller線程中維護的這個Selector標為主Selector。 Poller是NIO實現的主要線程。首先作為events queue的消費者,從queue中取出PollerEvent對象,然后將此對象中的channel以OP_READ事件注冊到主Selector中,然后主Selector執行select操作,遍歷出可以讀數據的socket,并從Worker線程池中拿到可用的Worker線程,然后將socket傳遞給Worker。整個過程是典型的NIO實現。
Worker Worker線程拿到Poller傳過來的socket后,將socket封裝在SocketProcessor對象中。然后從Http11ConnectionHandler中取出Http11NioProcessor對象,從Http11NioProcessor中調用CoyoteAdapter的邏輯,跟BIO實現一樣。在Worker線程中,會完成從socket中讀取http request,解析成HttpServletRequest對象,分派到相應的servlet并完成邏輯,然后將response通過socket發回client。在從socket中讀數據和往socket中寫數據的過程,并沒有像典型的非阻塞的NIO的那樣,注冊OP_READ或OP_WRITE事件到主Selector,而是直接通過socket完成讀寫,這時是阻塞完成的,但是在timeout控制上,使用了NIO的Selector機制,但是這個Selector并不是Poller線程維護的主Selector,而是BlockPoller線程中維護的Selector,稱之為輔Selector。
NioSelectorPool NioEndpoint對象中維護了一個NioSelecPool對象,這個NioSelectorPool中又維護了一個BlockPoller線程,這個線程就是基于輔Selector進行NIO的邏輯。以執行servlet后,得到response,往socket中寫數據為例,最終寫的過程調用NioBlockingSelector的write方法。

3.請求處理流程

上面介紹了Tomcat的總體架構和涉及到NIO的相關工作類,下面從一個網絡請求到Tomcat處理的過程直到業務servlet處理的過程,整體上說下一個網絡請求的處理流程,下面借用網上的一張流程圖,如果圖片作者看到覺得侵權請下面留言,馬上刪掉:)



對于Acceptor監聽到的Socket請求,經過NioEndpoint內部的NIO 線程模型處理后,會轉變為SocketProcessor在Executor中運行,其在Run過程中會交給Http11ConnectionHandler處理,Http11ConnectionHandler會從ConcurrentHashMap<NioChannel,Http11NioProcessor>緩存中獲取相應的Http11NioProcessor來繼續處理,Http11NioProcessor主要是負責解析socket請求Header,解析完成后,會將Request、Response(這里的請求、響應在tomcat中看成是coyote的請求、響應,意思是還需要CoyoteAdaper處理)交給CoyoteAdaper繼續處理,CoyoteAdaper這里的工作主要將socket解析的Request、Response轉化為HttpServletRequest、HttpServletResponse,而這里的請求響應就是最后交給Container去處理。
同時我們可以看到Acceptor線程會將接受到的SocketChannel(一個socket請求)封裝為PollerEvent放到Poller線程中的ConcurrentLinkedQueue<PollerEvent>緩存中,注意到這里的緩存是ConcurrentLinkedQueue是支持并發的,那么在Poller線程的內部,它只需要從這個緩存中不停地獲取PollerEvent然后處理就可以了。最后Poller線程處理完成后會封裝成SocketProcessor交給NioEndpoint內的線程池Executor去處理。線程池中的Work thread線程在處理SocketProcessor過程中,會調用Http11ConnectionHandler處理,而Http11ConnectionHandler則從ConcurrentHashMap<NioChannel,Http11NioProcessor>緩存中獲取相應的Http11NioProcessor來繼續處理,這里要注意的ConcurrentHashMap也是支持并發的。

4.NIO相關參數

一個或多個Acceptor線程,每個線程都有自己的Selector,Acceptor只負責accept新的連接,一旦連接建立之后就將連接注冊到其他Worker線程中

多個Worker線程,有時候也叫IO線程,就是專門負責IO讀寫的。一種實現方式就是像Netty一樣,每個Worker線程都有自己的Selector,可以負責多個連接的IO讀寫事件,每個連接歸屬于某個線程。另一種方式實現方式就是有專門的線程負責IO事件監聽,這些線程有自己的Selector,一旦監聽到有IO讀寫事件,并不是像第一種實現方式那樣(自己去執行IO操作),而是將IO操作封裝成一個Runnable交給Worker線程池來執行,這種情況每個連接可能會被多個線程同時操作,相比第一種并發性提高了,但是也可能引來多線程問題,在處理上要更加謹慎些。tomcat的NIO模型就是第二種。

所以一般參數就是Acceptor線程個數,Worker線程個數。
參考官方文檔https://tomcat.apache.org/tomcat-8.5-doc/config/http.html?spm=5176.100239.blogcont39093.5.Vomyf0
參數主要有以下幾個:
1)acceptCount
連接在被ServerSocketChannel accept之前就暫存在這個隊列中,acceptCount就是這個隊列的最大長度。ServerSocketChannel accept就是從這個隊列中不斷取出已經建立連接的的請求。所以當ServerSocketChannel accept取出不及時就有可能造成該隊列積壓,一旦滿了連接就被拒絕了;
2)acceptorThreadCount
Acceptor線程只負責從上述隊列中取出已經建立連接的請求。在啟動的時候使用一個ServerSocketChannel監聽一個連接端口如8080,可以有多個Acceptor線程并發不斷調用上述ServerSocketChannel的accept方法來獲取新的連接。參數acceptorThreadCount其實使用的Acceptor線程的個數;

  1. maxConnections
    這里就是tomcat對于連接數的一個控制,即最大連接數限制。一旦發現當前連接數已經超過了一定的數量(NIO默認是10000),上述的Acceptor線程就被阻塞了,即不再執行ServerSocketChannel的accept方法從隊列中獲取已經建立的連接。但是它并不阻止新的連接的建立,新的連接的建立過程不是Acceptor控制的,Acceptor僅僅是從隊列中獲取新建立的連接。所以當連接數已經超過maxConnections后,仍然是可以建立新的連接的,存放在上述acceptCount大小的隊列中,這個隊列里面的連接沒有被Acceptor獲取,就處于連接建立了但是不被處理的狀態。當連接數低于maxConnections之后,Acceptor線程就不再阻塞,繼續調用ServerSocketChannel的accept方法從acceptCount大小的隊列中繼續獲取新的連接,之后就開始處理這些新的連接的IO事件了;
  2. maxThread
    專門處理IO的Worker數,默認是200;

這篇文章從tomcat的整體架構入手,分別介紹了tomcat中的NIO相關類,也介紹了一個網絡請求在tomcat中的處理流程,最后介紹了一下tomcat中關鍵的幾個參數對NIO線程模式的作用和影響,相信會對希望了解tomcat nio線程模型的同學會有所幫助。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容