萬字詳解 Tomcat 組成與工作原理

-     Tomcat 是什麼    -

開源的 Java Web 應用服務器,實現了 Java EE(Java Platform Enterprise Edition) 的部 分技術規範,比如 Java Servlet、Java Server Page、JSTL、Java WebSocket。Java EE 是 Sun 公 司爲企業級應用推出的標準平臺,定義了一系列用於企業級開發的技術規範,除了上述的之外,還有 EJB、Java Mail、JPA、JTA、JMS 等,而這些都依賴具體容器的實現。

上圖對比了 Java EE 容器的實現情況,Tomcat 和 Jetty 都只提供了 Java Web 容器必需的 Servlet 和 JSP 規範,開發者要想實現其他的功能,需要自己依賴其他開源實現。

Glassfish 是由 sun 公司推出,Java EE 最新規範出來之後,首先會在 Glassfish 上進行實 現,所以是研究 Java EE 最新技術的首選。

最常見的情況是使用 Tomcat 作爲 Java Web 服務器,使用 Spring 提供的開箱即用的強大 的功能,並依賴其他開源庫來完成負責的業務功能實現。

-     Servlet 容器    -

Tomcat 組成如下圖:主要有 Container 和 Connector 以及相關組件構成。

Server:指的就是整個 Tomcat 服 務器,包含多組服務,負責管理和 啓動各個 Service,同時監聽 8005 端口發過來的 shutdown 命令,用 於關閉整個容器 。

Service:Tomcat 封裝的、對外提 供完整的、基於組件的 web 服務, 包含 Connectors、Container 兩個 核心組件,以及多個功能組件,各 個 Service 之間是獨立的,但是共享 同一 JVM 的資源 。

Connector:Tomcat 與外部世界的連接器,監聽固定端口接收外部請求,傳遞給 Container,並 將 Container 處理的結果返回給外部。

Container:Catalina,Servlet 容器,內部有多層容器組成,用於管理 Servlet 生命週期,調用 servlet 相關方法。

Loader:封裝了 Java ClassLoader,用於 Container 加載類文件;Realm:Tomcat 中爲 web 應用程序提供訪問認證和角色管理的機制。

JMX:Java SE 中定義技術規範,是一個爲應用程序、設備、系統等植入管理功能的框架,通過 JMX 可以遠程監控 Tomcat 的運行狀態。

Jasper:Tomcat 的 Jsp 解析引擎,用於將 Jsp 轉換成 Java 文件,並編譯成 class 文件。Session:負責管理和創建 session,以及 Session 的持久化 (可自定義),支持 session 的集 羣。

Pipeline:在容器中充當管道的作用,管道中可以設置各種 valve(閥門),請求和響應在經由管 道中各個閥門處理,提供了一種靈活可配置的處理請求和響應的機制。

Naming:命名服務,JNDI, Java 命名和目錄接口,是一組在 Java 應用中訪問命名和目錄服務的 API。命名服務將名稱和對象聯繫起來,使得我們可以用名稱訪問對象,目錄服務也是一種命名 服務,對象不但有名稱,還有屬性。Tomcat 中可以使用 JNDI 定義數據源、配置信息,用於開發 與部署的分離。

-     Container 組成    -

Engine:Servlet 的頂層容器,包含一 個或多個 Host 子容器;Host:虛擬主機,負責 web 應用的部 署和 Context 的創建;Context:Web 應用上下文,包含多個 Wrapper,負責 web 配置的解析、管 理所有的 Web 資源;Wrapper:最底層的容器,是對 Servlet 的封裝,負責 Servlet 實例的創 建、執行和銷燬。

生命週期管理 Tomcat 爲了方便管理組件和容器的生命週期,定義了從創建、啓動、到停止、銷燬共 12 中狀態,tomcat 生命週期管理了內部狀態變化的規則控制,組件和容器只需實現相應的生命週期 方法即可完成各生命週期內的操作 (initInternal、startInternal、stopInternal、 destroyInternal);

比如執行初始化操作時,會判斷當前狀態是否 New,如果不是則拋出生命週期異常;是的 話則設置當前狀態爲 Initializing,並執行 initInternal 方法,由子類實現,方法執行成功則設置當 前狀態爲 Initialized,執行失敗則設置爲 Failed 狀態;

Tomcat 的生命週期管理引入了事件機制,在組件或容器的生命週期狀態發生變化時會通 知事件監聽器,監聽器通過判斷事件的類型來進行相應的操作。事件監聽器的添加可以在 server.xml 文件中進行配置。

Tomcat 各類容器的配置過程就是通過添加 listener 的方式來進行的,從而達到配置邏輯與 容器的解耦。如 EngineConfig、HostConfig、ContextConfig。EngineConfig:主要打印啓動和停止日誌 HostConfig:主要處理部署應用,解析應用 META-INF/context.xml 並創建應用的 Context ContextConfig:主要解析併合並 web.xml,掃描應用的各類 web 資源 (filter、servlet、listener)。

-     Tomcat 的啓動過程    -

啓動從 Tomcat 提供的 start.sh 腳本開始,shell 腳本會調用 Bootstrap 的 main 方法,實際 調用了 Catalina 相應的 load、start 方法。

load 方法會通過 Digester 進行 config/server.xml 的解析,在解析的過程中會根據 xml 中的關係 和配置信息來創建容器,並設置相關的屬性。接着 Catalina 會調用 StandardServer 的 init 和 start 方法進行容器的初始化和啓動。

按照 xml 的配置關係,server 的子元素是 service,service 的子元素是頂層容器 Engine,每層容器有持有自己的子容器,而這些元素都實現了生命週期管理 的各個方法,因此就很容易的完成整個容器的啓動、關閉等生命週期的管理。

StandardServer 完成 init 和 start 方法調用後,會一直監聽來自 8005 端口 (可配置),如果接收 到 shutdown 命令,則會退出循環監聽,執行後續的 stop 和 destroy 方法,完成 Tomcat 容器的 關閉。同時也會調用 JVM 的 Runtime.getRuntime()﴿.addShutdownHook 方法,在虛擬機意外退 出的時候來關閉容器。

所有容器都是繼承自 ContainerBase,基類中封裝了容器中的重複工作,負責啓動容器相關的組 件 Loader、Logger、Manager、Cluster、Pipeline,啓動子容器 (線程池併發啓動子容器,通過 線程池 submit 多個線程,調用後返回 Future 對象,線程內部啓動子容器,接着調用 Future 對象 的 get 方法來等待執行結果)。

List<Future<Void>> results = new ArrayList<Future<Void>>();
for (int i = 0; i < children.length; i++) {
    results.add(startStopExecutor.submit(new StartChild(children[i])));
}
boolean fail = false;
for (Future<Void> result :results) {
    try {
        result.get();
    } catch (Exception e) {
        log.error(sm.getString("containerBase.threadedStartFailed"), e);
        fail = true;
    }
}

Web 應用的部署方式注:catalina.home:安裝目錄; catalina.base:工作目錄; 默認值 user.dir:

HostConfig 監聽了 StandardHost 容器的事件,在 start 方法中解析上述配置文件:

注:

ContextConfig 解析 context.xml 順序:

ContextConfig 解析 web.xml 順序:

注:

-     Servlet 生命週期    -****

Servlet 是用 Java 編寫的服務器端程序。其主要功能在於交互式地瀏覽和修改數據,生成動態 Web 內容。

  1. 請求到達 server 端,server 根據 url 映射到相應的 Servlet

  2. 判斷 Servlet 實例是否存在,不存在則加載和實例化 Servlet 並調用 init 方法

  3. Server 分別創建 Request 和 Response 對象,調用 Servlet 實例的 service 方法 (service 方法 內部會根據 http 請求方法類型調用相應的 doXXX 方法)

  4. doXXX 方法內爲業務邏輯實現,從 Request 對象獲取請求參數,處理完畢之後將結果通過 response 對象返回給調用方

  5. 當 Server 不再需要 Servlet 時 (一般當 Server 關閉時),Server 調用 Servlet 的 destroy() 方 法。

-     load on startup    -

當值爲 0 或者大於 0 時,表示容器在應用啓動時就加載這個 servlet; 當是一個負數時或者沒有指定時,則指示容器在該 servlet 被選擇時才加載; 正數的值越小,啓動該 servlet 的優先級越高。

single thread model

每次訪問 servlet,新建 servlet 實體對象,但並不能保證線程安全,同時 tomcat 會限制 servlet 的實例數目 最佳實踐:不要使用該模型,servlet 中不要有全局變量。

-     請求處理過程    -

  1. 根據 server.xml 配置的指定的 connector 以及端口監聽 http、或者 ajp 請求

  2. 請求到來時建立連接, 解析請求參數, 創建 Request 和 Response 對象, 調用頂層容器 pipeline 的 invoke 方法

  3. 容器之間層層調用, 最終調用業務 servlet 的 service 方法

  4. Connector 將 response 流中的數據寫到 socket 中

-     Pipeline 與 Valve    -

Pipeline 可以理解爲現實中的管道, Valve 爲管道中的閥門, Request 和 Response 對象在管道中 經過各個閥門的處理和控制。

每個容器的管道中都有一個必不可少的 basic valve, 其他的都是可選的, basic valve 在管道中最 後調用, 同時負責調用子容器的第一個 valve。

Valve 中主要的三個方法: setNext、getNext、invoke;valve 之間的關係是單向鏈式結構, 本身 invoke 方法中會調用下一個 valve 的 invoke 方法。

各層容器對應的 basic valve 分別是 StandardEngineValve、StandardHostValve、 StandardContextValve、StandardWrapperValve。

-     JSP 引擎    -

JSP 生命週期

JSP 元素代碼片段:<% 代碼片段 %> JSP 聲明:<%! declaration; [ declaration; ]+ ... %> JSP 表達式:<%= 表達式 %> JSP 註釋:<%-- 註釋 --%> JSP 指令:   <%@ directive attribute=“value” %> JSP 行爲:   <jsp:action_name attribute=“value” /> HTML 元素:html/head/body/div/p/… JSP 隱式對象:request、response、out、session、application、config、 pageContext、page、Exception

**JSP 元素說明 ** 代碼片段: 包含任意量的 Java 語句、變量、方法或表達式; JSP 聲明: 一個聲明語句可以聲明一個或多個變量、方法, 供後面的 Java 代碼使用; JSP 表達式: 輸出 Java 表達式的值, String 形式; JSP 註釋: 爲代碼作註釋以及將某段代碼註釋掉 JSP 指令: 用來設置與整個 JSP 頁面相關的屬性, <%@ page ... %> 定義頁面的依賴屬性, 比如 language、contentType、errorPage、 isErrorPage、import、isThreadSafe、session 等等 <%@ include ... %> 包含其他的 JSP 文件、HTML 文件或文本文件, 是該 JSP 文件的一部分, 會 被同時編譯執行 <%@ taglib ... %> 引入標籤庫的定義, 可以是自定義標籤 JSP 行爲: jsp:include、jsp:useBean、jsp:setProperty、jsp:getProperty、jsp:forward

-     Jsp 解析過程    -

-     Connector    -

Http:HTTP 是超文本傳輸協議, 是客戶端瀏覽器或其他程序與 Web 服務器之間的應用層通信協 議 AJP:Apache JServ 協議 (AJP) 是一種二進制協議, 專門代理從 Web 服務器到位於後端的應用 程序服務器的入站請求阻塞 IO。

非阻塞 IO

**** IO 多路複用 ****

阻塞與非阻塞的區別在於進行讀操作和寫操作的系統調用時,如果此時內核態沒有數據可讀或者沒有緩衝空間可寫時,是否阻塞。

IO 多路複用的好處在於可同時監聽多個 socket 的可讀和可寫事件,這樣就能使得應用可以同時監聽多個 socket,釋放了應用線程資源。

Tomcat 各類 Connector 對比

Connector 的實現模式有三種,分別是 BIO、NIO、APR,可以在 server.xml 中指定。

Apache Portable Runtime 是一個高度可移植的庫,它是 Apache HTTP Server 2.x 的核心。APR 具有許多用途,包括訪問高級 IO 功能(如 sendfile,epoll 和 OpenSSL),操作系統級功能(隨機數生成,系統狀態等)和本地進程處理(共享內存,NT 管道和 Unix 套接字)。

表格中字段含義說明:

NIO 處理相關類

Acceptor 線程負責接收連接,調用 accept 方法阻塞接收建立的連接,並對 socket 進行封裝成 PollerEvent,指定註冊的事件爲 op_read,並放入到 EventQueue 隊列中,PollerEvent 的 run 方法邏輯的是將 Selector 註冊到 socket 的指定事件。

Poller 線程從 EventQueue 獲取 PollerEvent,並執行 PollerEvent 的 run 方法,調用 Selector 的 select 方法,如果有可讀的 Socket 則創建 Http11NioProcessor,放入到線程池中執行。

CoyoteAdapter 是 Connector 到 Container 的適配器,Http11NioProcessor 調用其提供的 service 方法,內部創建 Request 和 Response 對象,並調用最頂層容器的 Pipeline 中的第一個 Valve 的 invoke 方法。

Mapper 主要處理 http url 到 servlet 的映射規則的解析,對外提供 map 方法。

-     NIO Connector 主要參數    -

Comet

Comet 是一種用於 web 的推送技術,能使服務器實時地將更新的信息傳送到客戶端,而無須客戶端發出請求 在 WebSocket 出來之前,如果不使用 comet,只能通過瀏覽器端輪詢 Server 來模擬實現服務器端推送。Comet 支持 servlet 異步處理 IO,當連接上數據可讀時觸發事件,並異步寫數據 (阻塞)。

Tomcat 要實現 Comet,只需繼承 HttpServlet 同時,實現 CometProcessor 接口:

Note:

異步 Servlet

傳統流程:

異步處理流程:

Servlet 線程將請求轉交給一個異步線程來執行業務處理,線程本身返回至容器,此時 Servlet 還沒有生成響應數據,異步線程處理完業務以後,可以直接生成響應數據(異步線程擁有 ServletRequest 和 ServletResponse 對象的引用)。

爲什麼 web 應用中支持異步?

推出異步,主要是針對那些比較耗時的請求:比如一次緩慢的數據庫查詢,一次外部 REST API 調用, 或者是其他一些 I/O 密集型操作。這種耗時的請求會很快的耗光 Servlet 容器的線程池,繼而影響可擴展性。

Note:從客戶端的角度來看,request 仍然像任何其他的 HTTP 的 request-response 交互一樣,只是耗費了更長的時間而已。

異步事件監聽

Note : onError/ onTimeout 觸發後,會緊接着回調 onComplete onComplete 執行後,就不可再操作 request 和 response。

作者:VectorJin

來源:juejin.cn/post/6844903473482317837

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/jyNAsqHaZ2EqA02LMyNyeg