歡迎光臨
每天分享高質量文章

談談 Tomcat 請求處理流程

來源: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 啟動以後會啟動一組執行緒用於不同階段的請求處理過程。

  1. Acceptor 執行緒組。用於接受新連線,並將新連線封裝一下,選擇一個 Poller 將新連線新增到 Poller 的事件佇列中。
  2. Poller 執行緒組。用於監聽 Socket 事件,當 Socket 可讀或可寫等等時,將 Socket 封裝一下新增到 worker 執行緒池的任務佇列中。
  3. worker 執行緒組。用於對請求進行處理,包括分析請求報文並建立 Request 物件,呼叫容器的 pipeline 進行處理。

Acceptor、Poller、worker 所在的 ThreadPoolExecutor 都維護在 NioEndpoint 中。

Connector Init and Start

  1. initServerSocket(),透過 ServerSocketChannel.open() 開啟一個 ServerSocket,預設系結到 8080 埠,預設的連線等待佇列長度是 100, 當超過 100 個時會拒絕服務。我們可以透過配置 conf/server.xml 中 Connector 的 acceptCount 屬性對其進行定製。
  2. createExecutor() 用於建立 Worker 執行緒池。預設會啟動 10 個 Worker 執行緒,Tomcat 處理請求過程中,Woker 最多不超過 200 個。我們可以透過配置 conf/server.xml 中 Connector 的 minSpareThreads 和 maxThreads 對這兩個屬性進行定製。
  3. Pollor 用於檢測已就緒的 Socket。 預設最多不超過 2 個,Math.min(2,Runtime.getRuntime().availableProcessors());。我們可以透過配置 pollerThreadCount 來定製。
  4. Acceptor 用於接受新連線。預設是 1 個。我們可以透過配置 acceptorThreadCount 對其進行定製。

Requtst Process

Acceptor

  1. Acceptor 在啟動後會阻塞在 ServerSocketChannel.accept(); 方法處,當有新連線到達時,該方法傳回一個 SocketChannel。
  2. 配置完 Socket 以後將 Socket 封裝到 NioChannel 中,並註冊到 Poller,值的一提的是,我們一開始就啟動了多個 Poller 執行緒,註冊的時候,連線是公平的分配到每個 Poller 的。NioEndpoint 維護了一個 Poller 陣列,當一個連線分配給 pollers[index] 時,下一個連線就會分配給 pollers[(index+1)%pollers.length].
  3. addEvent() 方法會將 Socket 新增到該 Poller 的 PollerEvent 佇列中。到此 Acceptor 的任務就完成了。

Poller

  1. selector.select(1000)。當 Poller 啟動後因為 selector 中並沒有已註冊的 Channel,所以當執行到該方法時只能阻塞。所有的 Poller 共用一個 Selector,其實現類是 sun.nio.ch.EPollSelectorImpl
  2. events() 方法會將透過 addEvent() 方法新增到事件佇列中的 Socket 註冊到 EPollSelectorImpl,當 Socket 可讀時,Poller 才對其進行處理
  3. createSocketProcessor() 方法將 Socket 封裝到 SocketProcessor 中,SocketProcessor 實現了 Runnable 介面。worker 執行緒透過呼叫其 run() 方法來對 Socket 進行處理。
  4. 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)。

  1. CoyoteAdapter 將 Rquest 提交給 Container 處理之前,並將 org.apache.coyote.Request 封裝到 org.apache.catalina.connector.Request,傳遞給 Container 處理的 Request 是 org.apache.catalina.connector.Request。
  2. 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

  1. allocate() 用來載入並初始化 Servlet,值的一提的是 Servlet 並不都是單例的,當 Servlet 實現了 SingleThreadModel 介面後,StandardWrapper 會維護一組 Servlet 實體,這是享元樣式。當然了 SingleThreadModel在 Servlet 2.4 以後就棄用了。
  2. createFilterChain() 方法會從 StandardContext 中獲取到所有的過濾器,然後將匹配 Request URL 的所有過濾器挑選出來新增到 filterChain 中。
  3. doFilter() 執行過濾鏈,當所有的過濾器都執行完畢後呼叫 Servlet 的 service() 方法。

Reference

  1. 《How Tomcat works》

    https://www.amazon.com/How-Tomcat-Works-Budi-Kurniawan/dp/097521280X

  2. 《Tomcat 架構解析》– 劉光瑞

    http://product.dangdang.com/25084132.html

  3. Tomcat-9.0-doc

    https://tomcat.apache.org/tomcat-9.0-doc/index.html

  4. apache-tomcat-9.0.0.M22-src

    http://www-eu.apache.org/dist/tomcat/tomcat-9/v9.0.0.M22/src/

  5. tomcat架構分析 (connector NIO 實現)

    http://gearever.iteye.com/blog/1844203

贊(0)

分享創造快樂