Tomcat總體架構(一)

Xu院生發表於2021-01-02

目錄

一、Server

二、Connector 和 Container(實際為Engine)

三、Context

四、Host

五、Wrapper

六、Container(真正的Container)

七、Lifecycle


一、Server

從最基本的功能來講,我們可以將伺服器描述為這樣一個應用 它接收其他計算機(客戶端)發來的請求資料並進行解析, 完成相關業務處理,然 後把處理結果作為響應返回給請求計算機(客戶端)。 通常情況下,我們通過使用Socket監聽伺服器指定埠來實現該功能。按照該描述,一個最簡單地伺服器設計如圖2-1所示。

二、Connector 和 Container(實際為Engine)

很快我們就會發現,將請求監聽與請求處理放到一起擴充套件性很差,比如當我們想適配多種網路協議,但是請求處理卻相同的時候。 自從Tomcat誕生起,它就始終支援與Apache整合,無論是通過AJP協議還是通過HTTP協議。 當Web應用通過Tomcat獨立部署時,我們選擇使用HTTP協議為客戶端提供服務; 當通過Apache進行叢集部署時,我們使用AJP協議與Wed伺服器(Apache )進行連結。應用伺服器(Tomcat)在兩種部署架構下切換時,應確保Web應用不需做任何變更。 那麼我們如何通過物件導向的方式來解決這個問題?自然的想法就是將網路協議與請求處理從概念上分離。 於是,我們做了如下改進(見圖2-2 )。

一個Server可以包含多個Connector和Container。其中:
Connector負責開啟Socket並監聽客戶端請求、返回響應資料; Container負責具體的請求處理。 Connector和Container分別擁有自己的start()和stop()方法來載入和釋放自己維護的資源。

但是,這個設計有個明顯的缺陷。既然Server可以包含多個Connector和Container,那麼如何知曉來自某個Connector的請求由哪個Container處理呢?
當然,我們可以維護一個複雜的對映規則來解決這個問題,但是這並不是必需的,後續章節你會發現Container的設計已經足夠靈活,並不需要一個Connector連結到多個Container。更合理的方式如圖2-3所示。

一個Server包含多個Service(它們互相獨立,只是共享一個JVM以及系統類庫), 一個Service負責維護多個Connector和一個Container,這樣來自Connector的請求只能由它所屬Service維護的Container處理。


在Tomcat中,Container是一個更加通用的概念。為了與Tomcat中的元件命名一致,我們將 Container重新命名為Engine,用以表示整個Servlet引擎。修改後的設計如圖2-4所示。

 需要注意此處的描述,Engine表示整個Servlet引擎,而非Servlet容器。表示整個Servlet容 器的是Server。引擎只負責請求的處理,並不需要考慮請求連結、協議等的處理。

三、Context

上面的設計已經解決了網路協議和容器的解耦,但是應用伺服器是用來部署並執行Web應用的,是一個執行環境,而不是一個獨立的業務處理系統。 因此,我們需要在Engine容器中支援管理Web應用,當接收到Connector的處理請求時,Engine容器能夠找到一個合適的Web應用來處理。

那麼在圖2-4的設計方案的基礎上,一種比較樸素的實現方案如圖2-5所示。

我們使用Context來表示一個Web應用,並且一個Engine可以包含多個Contex

 Context也擁有start()和stop()方法,用以在啟動時載入資源以及在停止時釋放資源。採 用這種方式設計,我們將載入和解除安裝資源的過程分解到每個元件當中,使元件充分解耦,提高伺服器的可擴充套件性和可維護性。在後續講解過程中,新增元件多數也會有相同方法,我們不再贅述。

四、Host

 這是不是個合理的方案呢?

設想我們有一臺主機,它承擔了多個域名的服務,如news.myCompany.com和 article.myCompany.com均由該主機處理,我們應如何實現呢?
當然,我們可以在該主機上執行多個伺服器例項,但是如果我們希望執行一個伺服器例項呢?
因為,作為應用伺服器,我們應提供儘量靈活的部署方式。 既然我們要提供多個域名的服務,那麼就可以將每個域名視為一個虛擬的主機,在每個虛擬主機下包含多個Web應用。因為對於客戶端使用者,他們並不瞭解服務端使用幾臺主機來為他們提供服務,只知道每個域名提供了哪些服務,因此,應用伺服器將每個域名抽象為一個虛擬主機從概念上是合理的.

根據這個想法修改後的設計如圖2-6所示。

我們用Host表示虛擬主機的概念,一個Host可以包含多個Context。

 在Tomcat的設計中,Engine既可以包含Host,又可以包含Context,這是由具體的Engine實現確定的,而且Tomcat採用一種通用的概念解決此問題,我們在後續部分會詳細講解。Tomcat提供的預設實現StandardEngine只能包含Host。

五、Wrapper

如果閱讀Servlet規範,我們就會知道,在一個Web應用中,可包含多個Servlet例項以處理來自不同連結的請求。因此,我們還需要一個元件概念來表示Servlet定義。 在Tomcat中,Servlet定義被稱為Wrapper,基於此修改後的設計如圖2-7所示.

六、Container(真正的Container)

截至目前,我們多次提到“容器”這個概念。儘管在具體的小節中,容器的含義並不相同,有時候指Engine,有時候指Context,但是它卻代表了一類元件,這類元件的作用就是處理接收自客戶端的請求並且返回響應資料。儘管具體操作可能會委派到子元件完成,但是從行為定義上,它們是一致的。 基於這個概念,我們再次修正了我們的設計,如圖2-8所示。

我們使用Container來表示容器, Container可以新增並維護子容器,因此Engine 、 Host , Context,Wrapper均繼承自Container。我們將它們之間的組合關係改為虛線,以表示它們之間是弱依賴的關係,即它們之間的關係是通過Container的父子容器的概念體現的。不過Service持有的是Engine介面 (8.5.6版本之前為Container介面,更加通用)。

 既然Tomcat的Container可以表示不同的概念級別:Servlet引擎、虛擬主機、Web應用和Servlet,那麼我們就可以將不同級別的容器作為處理客戶端請求的元件,這具體由我們提供的伺服器的複雜度決定。假使我們以嵌入式的方式啟動Tomcat,且執行極其簡單的請求處理,不必支援多Web應用的場景,那麼我們完全可以只在Service中維護一個簡化版的Engine ( 8.5.6之前甚至可以直接由Service維護一個Context)。當然,Tomcat的預設實現採用了圖2-8這種最靈活的方式,只是,我們要了解Tomcat的模型設計理論上的可伸縮性,這也是一箇中介軟體產品架構設計所需要重點關注的。

此外,Tomcat的Container還有一個很重要的功能,就是後臺處理。 在很多情況下,我們的Container需要執行一些非同步處理,而且是定期執行,如每隔30秒執行一次,Tomcat對於Web應用檔案變更的掃描就是通過該機制實現的。Tomcat針對後臺處理,在Container上定義了backgroundProcess()方法,並且其基礎抽象類( ContainerBase)確保在啟動元件的同時,非同步啟動後臺處理。因此,在絕大多數情況下,各個容器元件僅需要實現Container的background-Process()方法即可,不必考慮建立非同步執行緒。

七、Lifecycle

在進一步深入細化應用伺服器設計之前,我們希望從抽象和複用層面再審視一下當前的設計成果,使概念更加清晰,提供通用性定義用於應用伺服器的統一管理。 我們很容易發現,所有元件均存在啟動、停止等生命週期方法,擁有生命週期管理的特性。因此,我們可以基於生命週期管理進行一次介面抽象,如圖2-9所示。 我們針對所有擁有生命週期管理特性的元件抽象了一個Lifecycle通用介面,該介面定義了生命週期管理的核心方法。

  • Init():初始化元件。

  • start():啟動元件。

  • stop():停止元件。

  • destroy():銷燬元件。

同時,該介面支援元件狀態以及狀態之間的轉換,支援新增事件監聽器(LifecycleListener )用於監聽元件的狀態變化。如此,我們可以採用一致的機制來初始化、啟動、停止以及銷燬各個元件。如Tomcat核心元件的預設實現均繼承自LifecycleNBeanBase抽象類,該類不但負責元件各個狀態的轉換和事件處理,還將元件自身註冊為MBean,以便通過Tomcat的管理工具進行動態維護。

Tomcat中Lifecycle介面狀態圖如圖2-10所示。 首先,每個生命週期方法可能對應數個狀態的轉換,以start()為例,即分為啟動前、啟動中、已啟動,這3個狀態之間自動轉換(所有標識為auto的轉換路徑都是在生命週期方法中自動轉換的,不再需要額外的方法呼叫)。

其次,並不是每個狀態都會觸發生命週期事件,也不是所有生命週期事件均存在對應狀態。狀態與應用生命週期事件的對應如表2-1所示。

表2-1 Tomcat生命週期事件與狀態對映

方法狀態生命週期事件

init()

初始化中(INITIALIZING)

初始化前(BEFORE_INIT_EVENT)

已初始化(INITIALIZED)

初始化後(AFTER_INIT_EVENT)

start()

啟動前(STARTING_PREP)

啟動前(BEFORE_START_EVENT)

啟動中(STARTING)

啟動(START_EVENT)

已啟動(STARTED)

啟動後(AFTER_START_EVENT)

stop()

停止前(STOPPING_PREP)

停止前(BEFORE_STOP_EVENT)

停止中(STOPPING)

停止(STOP_EVENT)

已停止(STOPPED)

停止後(AFTER_STOP_EVENT)

destroy()

銷燬中(DESTROYING)

銷燬前(BEFORE_DESTROY_EVENT)

已銷燬(DESTROYED)

銷燬後(AFTER_DESTROY_EVENT)

  

週期事件(PERIODIC_EVENT)

  

配置啟動(CONFIGURE_START_EVENT)

  

配置停止(CONFIGURE_STOP_EVENT)

從表2-1中我們可以詳細地看到每個生命週期方法影響的元件狀態以及每個狀態觸發的事件。 此外,我們還注意到,Tomcat預設提供了3個與狀態無關的事件型別,其中PERIODIC_EVENT主要用於Container的後臺定時處理,每次呼叫後觸發該事件。CONFIGURE_START_EVENT和CONFIGURE_STOP_EVENT的使用在後續章節中將會講到。

相關文章