本系列文章将整理到我在GitHub上的《Java面試指南》倉庫,更多精彩内容請到我的倉庫裡檢視
https://github.com/h2pl/Java-Tutorial
喜歡的話麻煩點下Star哈
文章首發于我的個人部落格:
www.how2playlife.com
本文是微信公衆号【Java技術江湖】的《走進JavaWeb技術世界》其中一篇,本文部分内容來源于網絡,為了把本文主題講得清晰透徹,也整合了很多我認為不錯的技術部落格内容,引用其中了一些比較好的部落格文章,如有侵權,請聯系作者。
該系列博文會告訴你如何從入門到進階,從servlet到架構,從ssm再到SpringBoot,一步步地學習JavaWeb基礎知識,并上手進行實戰,接着了解JavaWeb項目中經常要使用的技術群組件,包括日志元件、Maven、Junit,等等内容,以便讓你更完整地了解整個Java Web技術體系,形成自己的知識架構。
為了更好地總結和檢驗你的學習成果,本系列文章也會提供每個知識點對應的面試題以及參考答案。
如果對本系列文章有什麼建議,或者是有什麼疑問的話,也可以關注公衆号【Java技術江湖】聯系作者,歡迎你參與本系列博文的創作和修訂。
很多東西在時序圖中展現的已經非常清楚了,沒有必要再一步一步的作介紹,是以本文以圖為主,然後對部分内容加以簡單解釋。
繪制圖形使用的工具是 PlantUML + Visual Studio Code + PlantUML Extension
本文對 Tomcat 的介紹以 Tomcat-9.0.0.M22 為标準。
Tomcat-9.0.0.M22 是 Tomcat 目前最新的版本,但尚未釋出,它實作了 Servlet4.0 及 JSP2.3 并提供了很多新特性,需要 1.8 及以上的 JDK 支援等等,詳情請查閱 Tomcat-9.0-doc。
https://tomcat.apache.org/tomcat-9.0-doc/index.html
Overview

Connector 啟動以後會啟動一組線程用于不同階段的請求處理過程。
- Acceptor 線程組。用于接受新連接配接,并将新連接配接封裝一下,選擇一個 Poller 将新連接配接添加到 Poller 的事件隊列中。
- Poller 線程組。用于監聽 Socket 事件,當 Socket 可讀或可寫等等時,将 Socket 封裝一下添加到 worker 線程池的任務隊列中。
- worker 線程組。用于對請求進行處理,包括分析請求封包并建立 Request 對象,調用容器的 pipeline 進行處理。
Acceptor、Poller、worker 所在的 ThreadPoolExecutor 都維護在 NioEndpoint 中。
Connector Init and Start
- initServerSocket(),通過 ServerSocketChannel.open() 打開一個 ServerSocket,預設綁定到 8080 端口,預設的連接配接等待隊列長度是 100, 當超過 100 個時會拒絕服務。我們可以通過配置 conf/server.xml 中 Connector 的 acceptCount 屬性對其進行定制。
- createExecutor() 用于建立 Worker 線程池。預設會啟動 10 個 Worker 線程,Tomcat 處理請求過程中,Woker 最多不超過 200 個。我們可以通過配置 conf/server.xml 中 Connector 的 minSpareThreads 和 maxThreads 對這兩個屬性進行定制。
- Pollor 用于檢測已就緒的 Socket。預設最多不超過 2 個,Math.min(2,Runtime.getRuntime().availableProcessors());。我們可以通過配置 pollerThreadCount 來定制。
- Acceptor 用于接受新連接配接。預設是 1 個。我們可以通過配置 acceptorThreadCount 對其進行定制。
Request Process
Acceptor
- Acceptor 在啟動後會阻塞在 ServerSocketChannel.accept(); 方法處,當有新連接配接到達時,該方法傳回一個 SocketChannel。
- 配置完 Socket 以後将 Socket 封裝到 NioChannel 中,并注冊到 Poller,值的一提的是,我們一開始就啟動了多個 Poller 線程,注冊的時候,連接配接是公平的配置設定到每個 Poller 的。NioEndpoint 維護了一個 Poller 數組,當一個連接配接配置設定給 pollers[index] 時,下一個連接配接就會配置設定給 pollers[(index+1)%pollers.length].
- addEvent() 方法會将 Socket 添加到該 Poller 的 PollerEvent 隊列中。到此 Acceptor 的任務就完成了。
Poller
- selector.select(1000)。當 Poller 啟動後因為 selector 中并沒有已注冊的 Channel,是以當執行到該方法時隻能阻塞。所有的 Poller 共用一個 Selector,其實作類是 sun.nio.ch.EPollSelectorImpl
- events() 方法會将通過 addEvent() 方法添加到事件隊列中的 Socket 注冊到 EPollSelectorImpl,當 Socket 可讀時,Poller 才對其進行處理
- createSocketProcessor() 方法将 Socket 封裝到 SocketProcessor 中,SocketProcessor 實作了 Runnable 接口。worker 線程通過調用其 run() 方法來對 Socket 進行處理。
- execute(SocketProcessor) 方法将 SocketProcessor 送出到線程池,放入線程池的 workQueue 中。workQueue 是 BlockingQueue 的執行個體。到此 Poller 的任務就完成了。
Worker
- worker 線程被建立以後就執行 ThreadPoolExecutor 的 runWorker() 方法,試圖從 workQueue 中取待處理任務,但是一開始 workQueue 是空的,是以 worker 線程會阻塞在 workQueue.take() 方法。
- 當新任務添加到 workQueue後,workQueue.take() 方法會傳回一個 Runnable,通常是 SocketProcessor,然後 worker 線程調用 SocketProcessor 的 run() 方法對 Socket 進行處理。
- createProcessor() 會建立一個 Http11Processor, 它用來解析 Socket,将 Socket 中的内容封裝到 Request 中。注意這個 Request 是臨時使用的一個類,它的全類名是 org.apache.coyote.Request,
- postParseRequest() 方法封裝一下 Request,并處理一下映射關系(從 URL 映射到相應的 Host、Context、Wrapper)。
- CoyoteAdapter 将 Rquest 送出給 Container 處理之前,并将 org.apache.coyote.Request 封裝到 org.apache.catalina.connector.Request,傳遞給 Container 處理的 Request 是 org.apache.catalina.connector.Request。
- connector.getService().getMapper().map(),用來在 Mapper 中查詢 URL 的映射關系。映射關系會保留到 org.apache.catalina.connector.Request 中,Container 處理階段 request.getHost() 是使用的就是這個階段查詢到的映射主機,以此類推 request.getContext()、request.getWrapper() 都是。
- connector.getService().getContainer().getPipeline().getFirst().invoke() 會将請求傳遞到 Container 處理,當然了 Container 處理也是在 Worker 線程中執行的,但是這是一個相對獨立的子產品,是以單獨分出來一節。
Container
- 需要注意的是,基本上每一個容器的 StandardPipeline 上都會有多個已注冊的 Valve,我們隻關注每個容器的 Basic Valve。其他 Valve 都是在 Basic Valve 前執行。
- request.getHost().getPipeline().getFirst().invoke() 先擷取對應的 StandardHost,并執行其 pipeline。
- request.getContext().getPipeline().getFirst().invoke() 先擷取對應的 StandardContext,并執行其 pipeline。
- request.getWrapper().getPipeline().getFirst().invoke() 先擷取對應的 StandardWrapper,并執行其 pipeline。
- 最值得說的就是 StandardWrapper 的 Basic Valve,StandardWrapperValve
- allocate() 用來加載并初始化 Servlet,值的一提的是 Servlet 并不都是單例的,當 Servlet 實作了 SingleThreadModel 接口後,StandardWrapper 會維護一組 Servlet 執行個體,這是享元模式。當然了 SingleThreadModel在 Servlet 2.4 以後就棄用了。
- createFilterChain() 方法會從 StandardContext 中擷取到所有的過濾器,然後将比對 Request URL 的所有過濾器挑選出來添加到 filterChain 中。
- doFilter() 執行過濾鍊,當所有的過濾器都執行完畢後調用 Servlet 的 service() 方法。
Reference
- 《How Tomcat works》
https://www.amazon.com/How-Tomcat-Works-Budi-Kurniawan/dp/097521280X
- 《Tomcat 架構解析》– 劉光瑞
http://product.dangdang.com/25084132.html
- Tomcat-9.0-doc
https://tomcat.apache.org/tomcat-9.0-doc/index.html
- apache-tomcat-9.0.0.M22-src
http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/
- tomcat架構分析 (connector NIO 實作)
http://gearever.iteye.com/blog/1844203
微信公衆号
個人公衆号:程式員黃小斜
微信公衆号【程式員黃小斜】新生代青年聚集地,程式員成長充電站。作者黃小斜,職業是阿裡程式員,身份是斜杠青年,希望和更多的程式員交朋友,一起進步和成長!專注于分享技術、面試、職場等成長幹貨,這一次,我們一起出發。
關注公衆号後回複“2020”領取我這兩年整理的學習資料,涵蓋自學程式設計、求職面試、算法刷題、Java技術學習、計算機基礎和考研等8000G資料合集。
技術公衆号:Java技術江湖
微信公衆号【Java技術江湖】一位阿裡 Java 工程師的技術小站,專注于 Java 相關技術:SSM、SpringBoot、MySQL、分布式、中間件、叢集、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術幹貨和學習經驗,緻力于Java全棧開發!
關注公衆号後回複“PDF”即可領取200+頁的《Java工程師面試指南》強烈推薦,幾乎涵蓋所有Java工程師必知必會的知識點。