Tomcat體系結構
我們可以從 server.xml 中就能夠看出 Tomcat 各組件的層次結構,具體結構圖如下:
- Server:代表整個容器,接收客戶端發來的請求數據並進行解析,完成相關業務處理,然後把處理結果作爲響應返回給客戶端。它可能包含一個或多個 Service 和全局 JNDI 資源;
- Service:包含一個或多個 Connector,這些連接器與一個 Engine 相關聯;
- Engine:表示這個Servlet引擎,而非Servlet容器。表示Servlet容器的是Server。Engine用於處理流水線(pipeline),它接收所有連接器的請求,並將響應交給適當的連接器返回給客戶端;
- Host:網絡名稱(域名)與 Tomcat 服務器的關聯,默認主機名 localhost,一個 Engine 可包含多個 Host;
- Connector:負責開啓Socket並監聽客戶端請求、返回響應數據。處理與客戶端的通信,網絡 I/O;
- Container:負責具體的請求處理。
- Context:表示一個 Web 應用程序,一個 Host 包含多個上下文。
架構模塊說明
Server
從某種意義來說,Server也算是一種應用,那種應用呢?
它可以接收其他計算機,也即是客戶端發來的請求,它處理請求,然後將處理後的結果返回給請求的客戶端。通常情況下,我們通過使用Socket監聽服務器指定端口來實現該功能。
按照最簡單的僅僅是用作嵌入在應用系統中的一個遠程請求處理方案,而且我們的系統訪問量很低,那麼我們可以設想下,Server只需要兩個操作就是可以了:start()和stop()。一個是啓動服務,一個關閉服務。如下圖
但我們需要的是一個應用服務器的話,就不能這麼簡單了。
Connector和Container
Connector: 負責請求的接收和響應
Container: 負責具體的請求處理
我們知道,應用服務器只有Server啓動和關閉是不行的。需要提供應用服務的功能,就要有接收需求、處理需求、響應需求的功能如下圖。爲什麼要將請求響應和處理分開不同的處理模塊。主要原因如下:
- 單一職責原則;
- 請求協議(HTTP/HTTPS/AJP等)有多種,而處理邏輯比較統一;
如上圖,但這樣也有問題,即Server中包含多個Connector和Container,那麼來個請求後,Connector怎麼知道這個請求交給哪個Container來處理呢?我們也可以在中間建立個映射來解決,但不是最優的,因爲後來會多出很多組件,這樣映射關係會越來越複雜。
那麼我們就引入一個Service組件,而這個組件,進行包裝和維護多個Connector和Container。而來自Connector的請求只能由它所屬Service維護的Container處理。結構圖如下:
Host、Context和Wrapper
現在我們的應用服務可以啓動,可以接受請求並處理請求。而且,多個請求方式,對應一個處理。那麼這個可以想到多個域名服務了。
如下兩個URL:
http://www.beijing.com/app/index.html
https://www.beijing.com/staple/list.html
這個應用均有一個主機處理,怎麼實現呢?
我們可以先看看Tomcat是如何進行配置和模塊定製化的,如下圖:
StandEngine, StandHost, StandContext及StandWrapper是容器,他們之間有互相的包含關係
爲什麼這麼設計?
- Wrapper封裝了具體的訪問資源,例如 index.html;
- Context 封裝了各個wrapper資源的應用名稱,例如 app;
- Host 封裝了各個context資源的集合;
按照領域模型,這個典型的URL訪問,可以解析出三層領域對象,他們之間互有隸屬關係。層層遞進,層層組合。那麼host的集合就是Engine。每個模塊各司其職,通過配置(server.xml)就可以很大限度的擴展Tomcat。設計的很靈活。
如果說以上三個容器是物理模型的封裝,那麼Engine可以作爲一種邏輯的封裝。 到此結構關係如下圖:
Valve
從Tomcat的結構圖可以看出,是按照層次,分別封裝成一個對象。
爲什麼要這樣做呢?
這主要是爲了方便統一管理。類似命名空間的概念,在不同層次的配置,其作用域不一樣。以tomcat自帶的打印request與response消息的RequestDumperValve
爲例。這個valve的類路徑是:
org.apache.catalina.valves.RequestDumperValve
valve機制是Tomcat非常重要的處理邏輯的機制。
如果這個valve配置在server.xml的Context節點下,則其只打印出訪問這個應用的的request與response消息。
如果這個valve配置在server.xml的Host節點下,則其可以打印出訪問這個Host下所有應用的request與response消息。
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
<Valve className="org.apache.catalina.valves.RequestDumperValve"/>
<Context path="/beijing" docBase="/usr/local/tomcat8/web/mingjing" ></Context>
<Context path="/shanghai" docBase="/usr/local/tomcat8/web/mingjing" ></Context>
</Host>
Lifecycle
在進一步深入細化應用服務器設計之前,我們希望從抽象和複用層面審視一下當前的設計,我們可以看出,所有的組件均有start()和stop()等生命週期方法,擁有生命週期管理的特性。所以就有了進一步的針對生命週期管理的接口抽象——Lifecycle。結構修改如下圖:
Tomcat中的組件都交給這個接口管理,但是具體組件的生命週期是由包含組件的父容器來管理的,Tomcat中頂級容器管理着Service的生命週期,Service容器又是Connector和Container的父容器,所以這兩個組件的生命週期是由Service管理的,Container也有子容器,所以管理着這些子容器的生命週期。這樣,只要所有組件都實現了Lifecycle接口,從頂層容器Server開始,就可以控制所有容器的生命週期了。而這裏使用了觀察者模式,包括Pipeline和Valve的責任鏈模式,有一篇Tomcat涉及的設計模式中有詳細講解。