Tomcat總體架構:Pipeline 和 Valve+Connector 設計+Executor等

2.1.5 Pipeline 和 Valve

從架構設計的角度來考慮,至此的應用服務器設計主要完成了我們對核心概念的分解,確保 了整體架構的可伸縮性和可擴展性,除此之外,我們還要考慮如何提高每個組件的靈活性,使其同樣易於擴展。

在增強組件的靈活性和可擴展性方面,職責鏈模式是一種比較好的選擇。Tomcat即採用該模 式來實現客戶端請求的處理——請求處理也是職責鏈模式典型的應用場景之一。換句話說,在 Tomcat中每個Container組件通過執行一個職責鏈來完成具體的請求處理。

Tomcat定義了Pipeline (管道)和Valve (閥)兩個接口。前者用於構造職責鏈,後者代表職 責鏈上的每個處理器。當然,我們還可以從字面意思來理解這兩個接口所扮演的角色一來自客 戶端的請求就像是流經管道的水一般,經過每個閥進行處理。其設計如圖2-11所示。Tomcat總體架構:Pipeline 和 Valve+Connector 設計+Executor等

Pipeline中維護了一個基礎的Valve,它始終位於Pipeline的末端(即最後執行),封裝了具體 的請求處理和輸出響應的過程。然後,通過addValve()方法,我們可以爲Pipeline添加其他的Valve 0 後添加的Valve位於基礎Valve之前,並按照添加順序執行。Pipeline通過獲得首個Valve來啓動整個鏈條的執行。

Tomcat容器組件的靈活之處在於,每個層級的容器(Engine, Host、Context, Wrapper )均有對應的基礎Valve實現,同時維護了一個Pipeline實例。也就是說,我們可以在任何層級的容器 上針對請求處理進行擴展。

由於Tomcat每個層級的容器均通過Pipeline和Valve進行請求處理,那麼,我們很容易將一些 通用的Valve實現根據需要添加到任何層級的容器上。

修改後的應用服務器設計如圖2-12所示。Tomcat總體架構:Pipeline 和 Valve+Connector 設計+Executor等

2.1.5 Connector 設計

前面我們重點討論了容器組件的設計,集中於如何設計才能確保容器的靈活性和可擴展性, 並做到合理的解耦。接下來,我們再細化一下服務器設計中的另一個重要組件一onnector0

要想與Container配合實現一個完整的服務器功能,Connector至少要完成如下幾項功能。

口監聽服務器端口,讀取來自客戶端的請求。

□將請求數據按照指定協議進行解析。

□根據請求地址匹配正確的容器進行處理。

□將響應返回客戶端。

只有這樣才能保證將接收到的客戶端請求交由與請求地址匹配的容器處理。我們知道,Tomcat支持多協議,默認支持HTTP和AJP。同時,Tomcat還支持多種I/O方式, 包括BIO ( 8.5版本之後移除)、NIO、APR。而且在Tomcat 8之後新增了對NIO2和HTTP/2協議的 支持。因此,對協議和I/O進行抽象和建模是需要重點關注的。

Tomcat的設計方案如圖2-13所示。Tomcat總體架構:Pipeline 和 Valve+Connector 設計+Executor等

在Tomcat中,ProtocolHandler表示一個協議處理器,針對不同協議和I/O方式,提供了不同的 實現,如Http 11 NioProtocol表示基於NIO的HTTP協議處理器。ProtocolHandler包含一個Endpoint 用於啓動Socket監聽,該接口按照I/O方式進行分類實現,如Nio2Endpoint表示非阻塞式Socket I/Oo 還包含一個Processor用於按照指定協議讀取數據,並將請求交由容器處理,如HttpllNioProcessor 表示在NIO的方式下HTTP請求的處理類。

注意 :Tomcat並沒有Endpoint接口,僅有AbstractEndpoint抽象類,此處僅作爲概念討論,故將其 視爲 Endpoint 接口 。

在Connector啓動時,Endpoint會啓動線程來監聽服務器端口,並在接收到請求後調用 Processor進行數據讀取。具體過程見後續章節。

當Processor賣取客戶端請求後,需要按照請求地址映射到具體的容器進行處理,這個過程即 爲請求映射。由於Tomcat各個組件釆用通用的生命週期管理,而且可以通過管理工具進行狀態變 更,因此請求映射除考慮映射規則的實現外,還要考慮容器組件的註冊與銷燬。

Tomcatil過Mapper和MapperListener兩個類實現上述功能。前者用於維護容器映射信息,同 時按照映射規則(Servlet規範定義)查找容器。後者實現了ContainerListener和LifecycleListener, 用於在容器組件狀態發生變更時,註冊或者取消對應的容器映射信息。爲了實現上述功能, MapperListener實現了Lifecycle接口,當其啓動時(在Service啓動時啓動),會自動作爲監聽器注 冊到各個容器組件上,同時將已創建的容器註冊到Mapper。

注意 在Tomcat7及之前的版本中,Mapper由Connector維護,而在Tomcat8中,改由Service維護, 因爲Service本來就是用於維護Connector和Container的組合,兩者從概念上講更密切一些。Tomcat通過適配器模式(Adapter )實現了Connector與Mapper、Container的解耦。Tomcat默 認的Connector實現(Coyote )對應的適配器爲CoyoteAdapter。也就是說,如果你希望使用Tomcat 的鏈接器方案,但是又想脫離Servlet容器(雖然這種情況幾乎不可能出現,但是從架構可擴展性 的角度來講,還是值得討論一下),此時只需要實現我們自己的Adapter即可。當然,我們還需要按照Container的定義開發我們自己的容器實現(不一定遵從Servlet規範)。

按照上述描述,Connector設計如圖2-14所示。Tomcat總體架構:Pipeline 和 Valve+Connector 設計+Executor等

2.1.5 Executor

完成了Connector的設計之後,我們再進一步審視一下當前的應用服務器方案,很明顯,我們 忽略了一個問題——併發。這對應用服務器而言是尤其需要考慮的,我們不可能讓所有來自客戶 端的請求均以串行的方式執行。那麼,我們應如何設計應用服務器的併發方案?

首先,回顧已經講解的Tomcat設計方案,既然Tomcat提供了一致的可插拔的組件環境,那麼 我們自然也希望線程池作爲一個組件進行統一管理。因此,Tomcat提供了Executor接口來表示一 個可以在組件間共享的線程池(默認使用了JDK5提供的線程池技術),該接口同樣繼承自 Lifecycle,可按照通用的組件進行管理。

其次,線程池的共享範圍如何確定?在Tomcat中Executor由Service維護,因此同一個Service 中的組件可以共享一個線程池。

當然,如果沒有定義任何線程池,相關組件(如Endpoint)會自動創建線程池,此時,線程 池不再共享。

在Tomcat中,Endpoint會啓動一組線程來監聽Socket端口,當接收到客戶端請求後,會創建 請求處理對象,並交由線程池處理,由此支持併發處理客戶端請求。

這裏我們僅從概念層面進行描述,Tomcat具體的線程池實現、使用方式、注意事項等會在後 續章節中詳細描述。

添加Executor後,總體設計如圖2-15所示。Tomcat總體架構:Pipeline 和 Valve+Connector 設計+Executor等

2.1.5 Bootstrap 和 Catalina

我們在前面幾個小節中講解了 Tomcat總體架構中的主要核心組件,它們代表了應用服務器程 序本身,這就如樓房的主體。但是,除了主體建築外,樓房還需要外牆等裝飾,Tomcat也一樣, 我們還需要提供一套配置環境來支持系統的可配置性,便於我們通過修改相關配置來優化應用服務器。

當然,我們沒有涉及集羣、安全等組件,儘管它們也非常重要,但是,我們還是希望更多 地關注於一些通用概念。雖然集羣、安全等作爲一個完備的應用服務器必不可少,但是它們的 缺失並不會影響我們去理解應用服務器的基本概念和設計方式。這些內容將會在後續章節中詳細講解。

在第1章中,我們列舉了Tomcat的幾個重要配置文件,其中最核心的文件爲server.xml。通過 這個文件,我們可以修改Tomcat組件的配置參數甚至添加相關組件,這也是後續性能調優階段重 點涉及的文件。

Tomcatil過類Catalina提供了一個Shell程序,用於解析server.xml創建各個組件,同時,負責 啓動、停止應用服務器(只需要啓動Tomcat頂層組件Server即可)。

Tomcat 使用Digester 解析 XML 文件,包括 server.xml 以及 web.xml 等,具體可參見 http://commons. apache.org/proper/commons-digester/,在講解Tomcat酉己置時,我們也會再做進一步說明。

最後,Tomcat提供了Bootstrap作爲應用服務器啓動入口。Bootstrap負責創建Catalina實例,根 據執行參數調用Catalina相關方法完成針對應用服務器的操作(啓動、停止)。

也許你會有疑問,爲什麼Tomcat不直接通過Catalina啓動,而是又提供了Bootstrap呢?你可 以查看一下Tomcat的發佈包目錄,Bootstrap並不位於Tomcat的依賴庫目錄下($CATALINA_ HOME/lib ),而是直接在$CATALINA_HOME/bin目錄下。Bootstrap與Tomcat應用服務器完全鬆耦 合(通過反射調用Catalina實例),它可以直接依賴JRE運行併爲Tomcat應用服務器創建共享類加 載器,用於構造Catalina實例以及整個Tomcat服務器。

注意 Tomcat的啓動方式可以作爲非常好的示範來指導中間件產品設計。它實現了啓動入口與 核心環境的解耦,這樣不僅簡化了啓動(不必配置各種依賴庫,因爲只有獨立的幾個API), 而且便於我們更靈活地組織中間件產品的結構,尤其是類加載器的方案,否則,我們所 有的依賴庫將統一放置到一個類加載器中,而無法做到靈活定製。

至此,我們應用服務器的完整設計如圖2-16所示。Tomcat總體架構:Pipeline 和 Valve+Connector 設計+Executor等

上述是Tomcat標準的啓動方式。但是正如我們所說,既然Server及其子組件代表了應用服務 器本身,那麼我們就可以不通過Bootstrap和Catalina來啓動服務器。

Tomcat提供了一個同名類org.apache.catalina.startup.Tomcat,使用它我們可以將Tomcat 服務器嵌入到我們的應用系統中並進行啓動。當然,你可以自己編寫代碼來啓動Server,也可以 自定義其他配置方式啓動,如YAML。這就是Tomcat靈活的架構設計帶給我們的便利,也是我們 設計中間件產品的架構關注點之一。

最後,我們再整體回顧一下上述講解涉及的Tomcat服務器中的概念,如表2-2所示。
Tomcat總體架構:Pipeline 和 Valve+Connector 設計+Executor等
至此,我們循序漸進地介紹了Tomcat總體架構的靜態設計,接下來我們將從兩個方面介紹 Tomcat的動態設計:應用服務器啓動和客戶端請求處理。這兩個方面也是應用服務器基本的處理 過程。

提前get tomcat PDF文檔:https://shimo.im/docs/TC9Jq63Tp6HvTXdg

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章