【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

Tomcat是什麼?html

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

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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

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

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

Servlet容器面試

Tomcat 組成shell

以下圖所示,主要有 Container 和 Connector 以及相關組件構成。數據庫

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

  • 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組成後端

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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

生命週期管理瀏覽器

Tomcat 爲了方便管理組件和容器的生命週期,定義了從建立、啓動、到中止、銷燬共 12 種狀態tomcat

tomcat 生命週期管理了內部狀態變化的規則控制,組件和容器只需實現相應的生命週期方法,便可完成各生命週期內的操做(initInternal、startInternal、stopInternal、 destroyInternal);

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

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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

Tomcat 各種容器的配置過程是經過添加 listener 的方式來進行的,從而達到配置邏輯與容器的解耦。如 EngineConfig、HostConfig、ContextConfig。

  • EngineConfig:主要打印啓動和中止日誌
  • HostConfig:主要處理部署應用,解析應用 META-INF/context.xml 並建立應用
  • Context ContextConfig:主要解析併合並 web.xml,掃描應用的各種 web 資源 (filter、servlet、listener)

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

Tomcat 的啓動過程

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

啓動從 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> results = new ArrayList<future>();

for (int i = 0; i < children.length; i++) {

results.add(startStopExecutor.submit(new StartChild(children[i])));

}

boolean fail = false;

for (Futureresult :results) {

try {

result.get();

} catch (Exception e) {

log.error(sm.getString("containerBase.threadedStartFailed"), e);

fail = true;

}

}

Web 應用的部署方式

注:catalina.home:安裝目錄;catalina.base:工做目錄;默認值 user.dir

  • Server.xml 配置 Host 元素,指定 appBase 屬性,默認\$catalina.base/webapps/
  • Server.xml 配置 Context 元素,指定 docBase,元素,指定 web 應用的路徑
  • 自定義配置:在\$catalina.base/EngineName/HostName/XXX.xml 配置 Context 元素

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

  • 掃描 appbase 路徑下的全部文件夾和 war 包,解析各個應用的 META-INF/context.xml,並 建立 StandardContext,並將 Context 加入到 Host 的子容器中。
  • 解析$catalina.base/EngineName/HostName/下的全部 Context 配置,找到相應 web 應 用的位置,解析各個應用的 META-INF/context.xml,並建立 StandardContext,並將 Context 加入到 Host 的子容器中。

注:

  • HostConfig 並無實際解析 Context.xml,而是在 ContextConfig 中進行的。
  • HostConfig 中會按期檢查 watched 資源文件(context.xml 配置文件)

ContextConfig 解析 context.xml 順序:

  • 先解析全局的配置 config/context.xml
  • 而後解析 Host 的默認配置 EngineName/HostName/context.xml.default
  • 最後解析應用的 META-INF/context.xml

ContextConfig 解析 web.xml 順序:

  • 先解析全局的配置 config/web.xml
  • 而後解析 Host 的默認配置 EngineName/HostName/web.xml.default 接着解析應用的 MEB-INF/web.xml
  • 掃描應用 WEB-INF/lib/下的 jar 文件,解析其中的 META-INF/web-fragment.xml 最後合併 xml 封裝成 WebXml,並設置 Context

注:

  • 掃描 web 應用和 jar 中的註解(Filter、Listener、Servlet)就是上述步驟中進行的。
  • 容器的按期執行:backgroundProcess,由 ContainerBase 來實現的,而且只有在頂層容器 中才會開啓線程。(backgroundProcessorDelay=10 標誌位來控制)

Servlet 生命週期

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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 中不要有全局變量

請求處理過程

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

  1. 根據 server.xml 配置的指定的 connector 以及端口監聽 http、或者 ajp 請求
  2. 請求到來時創建鏈接,解析請求參數,建立 Request 和 Response 對象,調用頂層容器 pipeline 的 invoke 方法
  3. 容器之間層層調用,最終調用業務 servlet 的 service 方法
  4. Connector 將 response 流中的數據寫到 socket 中

Pipeline 與 Valve

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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

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

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

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

JSP引擎

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

JSP 生命週期

  • 編譯階段:servlet 容器編譯 servlet 源文
  • 件,生成 servlet 類
  • 初始化階段:加載與 JSP 對應的 servlet 類, 建立其實例,並調用它的初始化方法
  • 執行階段:調用與 JSP 對應的 servlet 實例的 服務方法
  • 銷燬階段:調用與 JSP 對應的 servlet 實例的 銷燬方法,而後銷燬 servlet 實例

JSP元素

代碼片斷:<%>

JSP聲明:<%! ...="">

JSP表達式:<%=>

JSP註釋:<%-->

JSP指令:<%@ directive="" attribute="「value」">

JSP行爲:

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 解析過程

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

  • 代碼片斷:在_jspService()方法內直接輸出
  • JSP 聲明: 在 servlet 類中進行輸出
  • JSP 表達式:在_jspService()方法內直接輸出
  • JSP 註釋:直接忽略,不輸出
  • JSP 指令:根據不一樣指令進行區分,include:對引入的文件進行解析;page 相關的屬性會作爲 JSP 的屬性,影響的是解析和請求處理時的行爲
  • JSP 行爲:不一樣的行爲有不一樣的處理方式,jsp:useBean 爲例,會從 pageContext 根據 scope 的 類別獲取 bean 對象,若是沒有會建立 bean,同時存到相應 scope 的 pageContext 中
  • HTML:在_jspService()方法內直接輸出
  • JSP 隱式對象:在_jspService()方法會進行聲明,只能在方法中使用;

Connector

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

Http:HTTP 是超文本傳輸協議,是客戶端瀏覽器或其餘程序與 Web 服務器之間的應用層通訊協 議

AJP:Apache JServ 協議(AJP)是一種二進制協議,專門代理從 Web 服務器到位於後端的應用 程序服務器的入站請求

阻塞 IO

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

非阻塞 IO

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

IO多路複用

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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

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

Tomcat各種Connector對比

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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

  • JIO:用java.io編寫的TCP模塊,阻塞IO
  • NIO:用java.nio編寫的TCP模塊,非阻塞IO,(IO多路複用)
  • APR:全稱Apache Portable Runtime,使用JNI的方式來進行讀取文件以及進行網絡傳輸

Apache Portable Runtime是一個高度可移植的庫,它是Apache HTTP Server 2.x的核心。

APR具備許多用途,包括訪問高級IO功能(如sendfile,epoll和OpenSSL),操做系統級功能(隨機數生成,系統狀態等)和本地進程處理(共享內存,NT管道和Unix套接字)

表格中字段含義說明:

  • Support Polling:是否支持基於IO多路複用的socket事件輪詢
  • Polling Size:輪詢的最大鏈接數
  • Wait for next Request:在等待下一個請求時,處理線程是否釋放,BIO是沒有釋放的,因此在keep-alive=true的狀況下處理的併發鏈接數有限
  • Read Request Headers:因爲request header數據較少,能夠由容器提早解析完畢,不須要阻塞
  • Read Request Body:讀取request body的數據是應用業務邏輯的事情,同時Servlet的限制,是須要阻塞讀取的
  • Write Response:跟讀取request body的邏輯相似,一樣須要阻塞寫

NIO處理相關類

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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主要參數

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

Comet

Comet是一種用於web的推送技術,能使服務器實時地將更新的信息傳送到客戶端,而無須客戶端發出請求

在WebSocket出來以前,若是不適用comet,只能經過瀏覽器端輪詢Server來模擬實現服務器端推送。

Comet支持servlet異步處理IO,當鏈接上數據可讀時觸發事件,並異步寫數據(阻塞)

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

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

  • Begin:新的請求鏈接接入調用,可進行與Request和Response相關的對象初始化操做,並保存response對象,用於後續寫入數據
  • Read:請求鏈接有數據可讀時調用
  • End:當數據可用時,若是讀取到文件結束或者response被關閉時則被調用
  • Error:在鏈接上發生異常時調用,數據讀取異常、鏈接斷開、處理異常、socket超時

Note:

  • Read:在post請求有數據,但在begin事件中沒有處理,則會調用read,若是read沒有讀取數據,在會觸發Error回調,關閉socket
  • End:當socket超時,而且response被關閉時也會調用;server被關閉時調用
  • Error:除了socket超時不會關閉socket,其餘都會關閉socket
  • End和Error時間觸發時應關閉當前comet會話,即調用CometEvent的close方法
  • Note:在事件觸發時要作好線程安全的操做

異步Servlet

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

傳統流程:

  • 首先,Servlet 接收到請求以後,request數據解析;
  • 接着,調用業務接口的某些方法,以完成業務處理;
  • 最後,根據處理的結果提交響應,Servlet 線程結束

【深刻揭祕Tomcat服務器底層原理】扒下這隻又愛又恨的「Tom貓」

異步處理流程:

  • 客戶端發送一個請求
  • Servlet容器分配一個線程來處理容器中的一個servlet
  • servlet調用request.startAsync(),保存AsyncContext, 而後返回
  • 任何方式存在的容器線程都將退出,可是response仍然保持開放
  • 業務線程使用保存的AsyncContext來完成響應(線程池)
  • 客戶端收到響應

Servlet 線程將請求轉交給一個異步線程來執行業務處理,線程自己返回至容器,此時 Servlet 尚未生成響應數據,異步線程處理完業務之後,能夠直接生成響應數據(異步線程擁有 ServletRequest 和 ServletResponse 對象的引用)

爲何web應用中支持異步?

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

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

異步事件監聽

  • onStartAsync:Request調用startAsync方法時觸發
  • onComplete:syncContext調用complete方法時觸發
  • onError:處理請求的過程出現異常時觸發
  • onTimeout:socket超時觸發

Note :

onError/ onTimeout觸發後,會緊接着回調onComplete

onComplete 執行後,就不可再操做request和response

推薦閱讀:

牛皮了,馬士兵老師全網首播阿里P8級技術、實現大型淘寶實戰落

面試美團被JVM慘虐?阿里P9架構師用500分鐘把JVM從入門講到實戰#合集

清華啓蒙架構師馬士兵針對應屆生到開發十年的Java程序員作職業把脈

馬士兵教育:Spring源碼實戰全集,資深架構師帶你搞懂Spring源碼底層從入門到入墳

阿里P9架構師120分鐘帶你掌握線程池,不在爲線程而煩惱

相關文章
相關標籤/搜索