來源:Rainstorm ,
github.com/c-rainstorm/blog/blob/master/tomcat/談談Tomcat請求處理流程.md
建議結合《談談 Tomcat 架構及啟動過程[含部署]》一起看!
談談 Tomcat 架構及啟動過程[含部署]
http://www.importnew.com/27724.html
很多東西在時序圖中體現的已經非常清楚了,沒有必要再一步一步的作介紹,所以本文以圖為主,然後對部分內容加以簡單解釋。
繪製圖形使用的工具是 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 對其進行定製。
Requtst 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