前言
不少東西在時序圖中體現的已經很是清楚了,沒有必要再一步一步的做介紹,本文以圖爲主,而後對部份內容加以簡單解釋。
本文對 Tomcat 的介紹以 Tomcat-9.0.0.M22 爲標準。
Tomcat-9.0.0.M22 是 Tomcat 目前最新的版本,但還沒有發佈,它實現了 Servlet4.0 及 JSP2.3 並提供了不少新特性,須要 1.8 及以上的 JDK 支持等等,詳情請查閱 Tomcat-9.0-doc
-
Overview
-
Connector Init and Start
-
Requtst Process
-
Acceptor
-
Poller
-
Worker
-
Container
-
At last
Overview
-
Connector 啓動之後會啓動一組線程用於不一樣階段的請求處理過程。
-
Acceptor 線程組。用於接受新鏈接,並將新鏈接封裝一下,選擇一個 Poller 將新鏈接添加到 Poller 的事件隊列中。
-
Poller 線程組。用於監聽 Socket 事件,當 Socket 可讀或可寫等等時,將 Socket 封裝一下添加到 worker 線程池的任務隊列中。
-
worker 線程組。用於對請求進行處理,包括分析請求報文並建立 Request 對象,調用容器的 pipeline 進行處理。
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》
-
《Tomcat 架構解析》-- 劉光瑞
-
Tomcat-9.0-doc
-
apache-tomcat-9.0.0.M22-src
-
tomcat 架構分析 (connector NIO 實現)
最後
歡迎你們關注個人公衆號【程序員追風】,文章都會在裏面更新,整理的資料也會放在裏面。