
在了解Tomcat各個核心組件的時候強烈建議大家讀下源碼,之前也看過好多次tomcat的文章,但是基本都是看的時候明白,看過就忘記了。如果伴著源碼去讀下各個組件能更好的理解并加深記憶,光看博文不實際去動手試試是難以真正理解的?!凹埳系脕斫K覺淺,絕知此事要躬行”學習任何東西都是這個道理。

Tomcat整體的組件架構(gòu)如上圖所示,這個跟server.xml中的配置是保持一致的。頂級組件為Server跟Service,一個Tomcat中只有一個Server,Server下可以啟動多個Service。Service下最主要的兩大組件Connector跟Container,Connector負責接收請求包裝成request、response,而Container負責處理請求返回response。下面我們來從上到下介紹下每個組件。
Server
Server代表完整的Tomcat實例在Java虛擬集中是單例,主要是用來管理容器下各個Serivce組件的生命周期。下圖描述了Server組件的關鍵方面。如圖所示,Server實例是通過server.xml配置文件來配置的;其根元素所代表的正是Tomcat實例,默認實現(xiàn)為org.apache.catalina.core.StandardServer。但是,你也可以通過標簽的class屬性來自定義服務器實現(xiàn)。

服務器重要的一方面就是它打開了8005端口(默認端口)來監(jiān)聽關閉服務命令(默認情況下命令為SHUTDOWN)。當收到shutdown命令后,服務器會優(yōu)雅的關閉自己。同時出于安全考慮,發(fā)起關閉請求的連接必須來自同一臺機器上的同一個運行中的Tomcat實例。
此外,Server還提供了一個Java命名服務和JNDI服務,可以通過這兩個服務可以使用名稱來注冊專用對象(如數(shù)據(jù)源配置)。在運行期,單個組件(如Servlet)可以使用對象名稱來通過服務器的JNDI綁定服務來查找需要的對象相關信息。
Service
Service將一組Connector組件和Engine關聯(lián)起來,代表Tomcat中一組請求處理的組件。Service僅僅是一個分組結(jié)構(gòu),它并不包含任何其他的附加功能。

一個Service集中了一些連接器,每個連接器監(jiān)控一個指定的IP及端口并通過指定的協(xié)議做出響應??蛻舳苏埱笫紫鹊竭_連接器(Connector),連接器在再將這些請求輪流傳入Engine中處理,Engine是Tomcat中請求處理的關鍵組件。上圖中展示了HTTP連接器、HTTPS連接以及AJP組件。
一般很少會修改這個元素,默認的Service實例通常就足夠使用了。
Connector
Connector是客戶端連接到Tomcat容器的服務點,它為引擎提供協(xié)議服務來將引擎與客戶端各種協(xié)議隔離開來,如HTTP、HTTPS、AJP協(xié)議。
Connector需要完成的核心工作是:1)網(wǎng)絡通信2)應用層協(xié)議解析3)Tomcat Request/Response 與 ServletRequest/ServletResponse 的轉(zhuǎn)化。為了完成這些事情Tomcat 的設計者設計了 3 個組件來實現(xiàn)這 3 個功能,分別是 EndPoint、Processor 和 Adapter,其中 Endpoint和 Processor放在一起抽象成了 ProtocolHandler組件。一個請求來了處理的過程如下圖所示。

ProtocolHandler
Tomcat 支持的應用層協(xié)議有:
- HTTP/1.1:這是大部分 Web 應用采用的訪問協(xié)議。
- AJP:用于和 Web 服務器集成(如 Apache)。
- HTTP/2:HTTP 2.0 大幅度的提升了 Web 性能。
Tomcat支持的 I/O 模型有:
- NIO:非阻塞 I/O,采用 Java NIO 類庫實現(xiàn)。
- NIO2:異步I/O,采用 JDK 7 最新的 NIO2 類庫實現(xiàn)。
- APR:采用 Apache可移植運行庫實現(xiàn),是 C/C++ 編寫的本地庫。
應用層協(xié)議是變化的,I/O模型也是變化的,但是整體的處理邏輯是不變的,EndPoint 負責提供字節(jié)流給 Processor,Processor負責提供 Tomcat Request 對象給 Adapter,Adapter負責提供 ServletRequest對象給容器。Tomcat使用模板方法設計模式,設計了一系列抽象基類來封裝這些穩(wěn)定的部分,抽象基類 AbstractProtocol實現(xiàn)了 ProtocolHandler接口。每一種應用層協(xié)議有自己的抽象基類,比如 AbstractAjpProtocol和 AbstractHttp11Protocol,具體協(xié)議的實現(xiàn)類擴展了協(xié)議層抽象基類。

EndPoint

要了解EndPoint可以先看這張圖,已NioEndPoint為例,EndPoint中主要包含LimitLatch、Acceptor、Poller、SocketProcessor 和Executor 5 個組件,共同來完成對TCP/IP協(xié)議的處理。
LimitLatch是連接控制器,它負責控制最大連接數(shù),NIO 模式下默認是 10000,達到這個閾值后,連接請求被拒絕。
Acceptor線程組,單獨線程,死循環(huán),用于接受新連接,并將新連接封裝一下,選擇一個Poller將SocketChannel添加到 Poller 的事件隊列中。
Poller線程組,也是單獨線程,死循環(huán)。每個Poller線程都有自己的Queue。每個Poller線程可能被多個 Acceptor線程注冊PollerEvent。Poller不斷的通過內(nèi)部的Selector對象向內(nèi)核查詢Channel的狀態(tài),一旦可讀就生成任務類 SocketProcessor 交給 Executor 去處理。Poller 的另一個重要任務是循環(huán)遍歷檢查自己所管理的 SocketChannel是否已經(jīng)超時,如果有超時就關閉這個SocketChannel。Acceptor與Poller線程之間通過Queue通信,典型的生產(chǎn)者消費者。
SocketProcessor主要是創(chuàng)建相應的Processor,并將SocketWrapperBase對象交由Processor處理。
Executor就是線程池,這個線程池維護的線程就是我們非常熟悉的“http-nio-8080-exec-N”線程,也就是用戶請求的實際處理線程。主要負責運行SocketProcessor任務類,以及后續(xù)的處理。
Processor
Processor 主要是對應用層協(xié)議的抽象。Processor 接收來自 EndPoint 的 SocketWrapper,讀取字節(jié)流做解析,例如Http11Processor會按照HTTP1.1協(xié)議進行數(shù)據(jù)解析,構(gòu)建 Tomcat Request 和 Response 對象,并通過 Adapter 將其提交到容器處理。
Adapter
由于協(xié)議的不同,Tomcat 定義了自己的 Request 類來存放請求信息,這里其實體現(xiàn)了面向?qū)ο蟮乃季S。但是這個 Request 不是標準的 ServletRequest ,所以不能直接使用 Tomcat 定義 Request 作為參數(shù)直接容器。
Tomcat 設計者的解決方案是引入 CoyoteAdapter,這是適配器模式的經(jīng)典運用,連接器調(diào)用 CoyoteAdapter 的 Sevice 方法,傳入的是 Tomcat Request 對象,CoyoteAdapter負責將 Tomcat Request 轉(zhuǎn)成 ServletRequest,再調(diào)用容器的 Service方法。在轉(zhuǎn)換 ServletRequest 的時候會對請求鏈接進行解析,映射到請求指定的 Host 以及 Context 上(存放在ServletRequest中的MappingData),為了后續(xù)傳遞給容器提供基礎。
Container
容器是用于封裝和管理Servlet,以及具體處理Request請求。它由四個子容器組件構(gòu)成,分別是Engine、Host(站點)、Context(應用)、Wrapper(Servlet)。這四個組件不是平行關系,而是父子包含關系。除Engine外都可存在多個。

你可能會問,為啥要設計這么多層次的容器,這不是增加復雜度么?其實這背后的考慮是,Tomcat 通過一種分層的架構(gòu),使得 Servlet 容器具有很好的靈活性。因為這里正好符合一個 Host 多個 Context, 一個 Context 也包含多個 Servlet,而每個組件都需要統(tǒng)一生命周期管理,所以使用組合模式設計這些容器。
public interface Container extends Lifecycle {
public void setName(String name);
public Container getParent();
public void setParent(Container container);
public void addChild(Container child);
public void removeChild(Container child);
public Container findChild(String name);
}
所有容器組件都實現(xiàn)了 Container 接口,因此在使用容器組件的時候具有一致性。我們看到了getParent、SetParent、addChild和 removeChild等方法,這里就是通過組合模式來統(tǒng)一管理所有容器。我們還看到 Container 接口拓展了 Lifecycle ,Tomcat 就是通過 Lifecycle 統(tǒng)一管理所有容器組件的生命周期。
Engine
引擎表示可運行的Catalina的Servlet引擎實例。在一個服務中只能有一個引擎,同時,作為一個真正的容器,Engine元素之下可以包含一個或多個虛擬主機。
作為請求處理的主要組件,它接收Connector傳入請求的對象以及輸出相應結(jié)果。Engine沒有復雜的功能,代碼也只有400多行,它主要功能是將傳入請求委托給適當?shù)奶摂M主機處理。

Host
Host 顧名思義就是虛擬主機。Host 是 Engine 的子容器,一個 Host 在 Engine 中代表一個虛擬主機,這個虛擬主機的作用就是運行應用,它負責安裝和展開這些應用,并且標識這個應用以便能夠區(qū)分它們。它的子容器是 Context,它除了關聯(lián)子容器外,還有就是保存一個主機應該有的信息。每個虛擬主機下都可以部署一個或者多個 Web App,每個 Web App 對應于一個 Context,當 Host 獲得一個請求時,會將請求轉(zhuǎn)發(fā)到某個 Context 來處理。

像上圖所示,不同域名的請求在經(jīng)過 Engine 后會交由不同的 Host 來處理(如果找不到指定的Host會交由默認的 Host 來處理)。但是現(xiàn)實中為了保證每個服務的穩(wěn)定性、隔離性等,基本上都是一臺機器只會部署一個 Host(localhost),也是默認的 Host。同時如果每個服務都配置相應的域名的話也違背了依賴倒置原則,造成擴展性比較差。
Context
Context它表示就是Web應用程序本身(我們部署在Tomcat中的應用),它具備了 Servlet 運行的基本環(huán)境。理論上只要有 Context 就能運行 Servlet 了。簡單的 Tomcat 可以沒有 Engine 和 Host。Context 最重要的功能就是管理它里面的 Servlet 實例,Servlet 實例在 Context 中是以 Wrapper 出現(xiàn)的。

Wrapper
Wrapper是 Context 容器的子容器,表示一個 Servlet(或者由jsp文件轉(zhuǎn)換而來的servlet)。它之所以稱為包裝器是因為它包裝了java.servlet.Servlet實例。這是容器層次結(jié)構(gòu)的最底層,添加任何子類都會導致異常。

Wrapper負責它所包裝的Servlet的整個生命周期,包括加載、初始化、執(zhí)行、回收,執(zhí)行Servlet的方法如init()、service()、destroy()。
其他組件
Valve
注意是Valve不是Value,開始的時候還因為看錯迷惑了半天。Valve是處理元素,它可以被包含在每個Tomcat容器的中,負責處理請求,并將請求傳遞給下層容器。但是對于容器來說,它并不會直接持有Valve的引用,而是會持有一個稱作管道Pipeline 的單一實體的引用,并用這個管道來關聯(lián)的一系列Valve。
public interface Valve {
public Valve getNext();
public void setNext(Valve valve);
public void invoke(Request request, Response response)
}
public interface Pipeline {
public void addValve(Valve valve);
public Valve getBasic();
public void setBasic(Valve valve);
public Valve getFirst();
}
Pipeline+Valve使用的是典型的責任鏈模式,Pipeline中有addValve方法,維護了Valve鏈表。Pipeline 中沒有 invoke 方法,因為整個調(diào)用鏈的觸發(fā)是 Valve 來完成的,Valve完成自己的處理后,調(diào)用 getNext.invoke() 來觸發(fā)下一個 Valve 調(diào)用。
Pipeline中還有個 getBasic方法。BasicValve處于 Valve鏈表的末端,它是 Pipeline中必不可少的一個 Valve,負責調(diào)用下層容器的 Pipeline 里的第一個 Valve。

Tomcat中的請求傳遞過程如上圖所示,Value的機制跟Filter的機制非常像,主要是有以下的區(qū)別
- Valve是 Tomcat的私有機制,與 Tomcat 的基礎架構(gòu) API是緊耦合的。Servlet API是公有的標準,所有的 Web 容器包括 Jetty 都支持 Filter 機制。
- Valve工作在 Web 容器級別,攔截所有應用的請求;而 Servlet Filter 工作在應用級別,只能攔截某個 Web 應用的所有請求。如果想做整個 Web容器的攔截器,必須通過 Valve來實現(xiàn)。
京東年薪60W架構(gòu)師帶你深入拆解Tomcat,Endpoint
Apache Tomcat 8 Configuration Reference
詳解Tomcat系列(一)-從源碼分析Tomcat的啟動
Tomcat系列(4)——Tomcat 組件及架構(gòu)詳細部分
談談 Tomcat 請求處理流程