深入理解Tomcat 基本架構

The Apache Tomcat® software is an open source implementation of the Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket technologies. The Java Servlet, JavaServer Pages, Java Expression Language and Java WebSocket specifications are developed under the Java Community Process.

前言

互聯網的本質,是數據遷移的過程。

以Java常用框架體系爲例。

瀏覽器請求->Servlet容器->Restful框架->ORM框架->DB。

整個數據流,基本是這個過程。

各階段常用的框架:

  • Servlet容器
    • tomcat
    • jetty
    • undertow
  • Restful風格框架
    • springboot
    • Jersey
  • ORM
    • hibernate
    • mybatis
    • tk. mybatis
    • mybatis-plus
    • springboot jpa
  • DB
    • mysql

還有一些中間件:rabbitMQ、Redis。

本文在深入拆解Tomcat & Jetty 的基礎上,總結學習。

基本原理

Servlet接口和Servlet容器這一整套規範叫作Servlet規範。

Tomcat和Jetty都按照Servlet規範的要求實現了Servlet容器,同時它們也具有HTTP服務器的功能。

Tomcat要實現2個核心功能:

  • 處理Socket連接,負責網絡字節流與Request和Response對象的轉化。

  • 加載和管理Servlet,以及具體處理Request請求。

Tomcat設計了兩個核心組件連接器(Connector)和容器(Container)來分別做這兩件事情。連接器負責對外交流,容器負責內部處理。

最頂層是Server,這裏的Server指的就是一個Tomcat實例。

一個Server中有一個或者多個Service,一個Service中有多個連接器和一個容器。

連接器與容器之間通過標準的ServletRequest和ServletResponse通信。

連接器 Connector

Tomcat支持的I/O模型有:

  • NIO:非阻塞I/O,採用Java NIO類庫實現。

  • NIO.2:異步I/O,採用JDK 7最新的NIO.2類庫實現。

  • APR:採用Apache可移植運行庫實現,是C/C++編寫的本地庫。

  • IO多路複用,採用NioEndpoint組件實現

Tomcat中的I/O模型在這裏

連接器對Servlet容器屏蔽了協議及I/O模型等的區別,無論是HTTP還是AJP,在容器中獲取到的都是一個標準的ServletRequest對象。

  • Endpoint:負責網絡通信,提供字節流給Processor。

  • Processor:應用層協議解析,負責提供Tomcat Request對象給Adapter。

  • Adapter:Tomcat Request/Response與ServletRequest/ServletResponse的轉化。

由於I/O模型和應用層協議可以自由組合,比如NIO + HTTP或者NIO.2 + AJP。Tomcat的設計者將網絡通信和應用層協議解析放在一起考慮,設計了一個叫ProtocolHandler的接口來封裝這兩種變化點。

除了這些變化點,系統也存在一些相對穩定的部分,因此Tomcat設計了一系列抽象基類來封裝這些穩定的部分,抽象基類AbstractProtocol實現了ProtocolHandler接口。

每一種應用層協議有自己的抽象基類,具體協議的實現類擴展了協議層抽象基類。

連接器模塊有三個核心組件:Endpoint、Processor和Adapter。

其中Endpoint和Processor放在一起抽象成了ProtocolHandler組件。

ProtocolHandler組件

連接器用ProtocolHandler來處理網絡連接和應用層協議。

Endpoint

Endpoint是通信端點,即通信監聽的接口,是具體的Socket接收和發送處理器,是對傳輸層的抽象,因此Endpoint是用來實現TCP/IP協議的。

Endpoint是一個接口,對應的抽象實現類是AbstractEndpoint,而AbstractEndpoint的具體子類,比如在NioEndpoint和Nio2Endpoint中,有兩個重要的子組件:Acceptor和SocketProcessor。

其中Acceptor用於監聽Socket連接請求。SocketProcessor用於處理接收到的Socket請求,它實現Runnable接口,在run方法裏調用協議處理組件Processor進行處理。爲了提高處理能力,SocketProcessor被提交到線程池來執行。而這個線程池叫作執行器(Executor),我在後面的專欄會詳細介紹Tomcat如何擴展原生的Java線程池。

Processor

如果說Endpoint是用來實現TCP/IP協議的,那麼Processor用來實現HTTP協議,Processor接收來自Endpoint的Socket,讀取字節流解析成Tomcat Request和Response對象,並通過Adapter將其提交到容器處理,Processor是對應用層協議的抽象。

Tomcat支持的應用層協議有:

  • HTTP/1.1:這是大部分Web應用採用的訪問協議。

  • AJP:用於和Web服務器集成(如Apache)。

  • HTTP/2:HTTP 2.0大幅度的提升了Web性能。

Processor是一個接口,定義了請求的處理等方法。它的抽象實現類AbstractProcessor對一些協議共有的屬性進行封裝,沒有對方法進行實現。具體的實現有AjpProcessor、Http11Processor等,這些具體實現類實現了特定協議的解析方法和請求處理方式。

從圖中我們看到,Endpoint接收到Socket連接後,生成一個SocketProcessor任務提交到線程池去處理,SocketProcessor的run方法會調用Processor組件去解析應用層協議,Processor通過解析生成Request對象後,會調用Adapter的Service方法。

Adapter組件

由於協議不同,客戶端發過來的請求信息也不盡相同,Tomcat定義了自己的Request類來“存放”這些請求信息。ProtocolHandler接口負責解析請求並生成Tomcat Request類。但是這個Request對象不是標準的ServletRequest,也就意味着,不能用Tomcat Request作爲參數來調用容器。Tomcat設計者的解決方案是引入CoyoteAdapter,這是適配器模式的經典運用,連接器調用CoyoteAdapter的sevice方法,傳入的是Tomcat Request對象,CoyoteAdapter負責將Tomcat Request轉成ServletRequest,再調用容器的service方法。

容器

Tomcat設計了4種容器,分別是Engine、Host、Context和Wrapper。

這4種容器不是平行關係,而是父子關係。

Tomcat通過一種分層的架構,使得Servlet容器具有很好的靈活性。

  • Context表示一個Web應用程序;
  • Wrapper表示一個Servlet,一個Web應用程序中可能會有多個Servlet;
  • Host代表的是一個虛擬主機,或者說一個站點,可以給Tomcat配置多個虛擬主機地址,而一個虛擬主機下可以部署多個Web應用程序;
  • Engine表示引擎,用來管理多個虛擬站點,一個Service最多隻能有一個Engine。

Tomcat採用了組件化的設計,它的構成組件都是可配置的,其中最外層的是Server,其他組件按照一定的格式要求配置在這個頂層容器中。

請求定位Servlet的過程

Mapper組件

Mapper組件的功能就是將用戶請求的URL定位到一個Servlet。

它的工作原理是: Mapper組件裏保存了Web應用的配置信息,其實就是容器組件與訪問路徑的映射關係,比如Host容器裏配置的域名、Context容器裏的Web應用路徑,以及Wrapper容器裏Servlet映射的路徑,你可以想象這些配置信息就是一個多層次的Map。

當一個請求到來時,Mapper組件通過解析請求URL裏的域名和路徑,再到自己保存的Map裏去查找,就能定位到一個Servlet。請你注意,一個請求URL最後只會定位到一個Wrapper容器,也就是一個Servlet。

案例

假如有一個網購系統,有面向網站管理人員的後臺管理系統,還有面向終端客戶的在線購物系統。

這兩個系統跑在同一個Tomcat上,爲了隔離它們的訪問域名,配置了兩個虛擬域名:manage.shopping.com和user.shopping.com,

網站管理人員通過manage.shopping.com域名訪問Tomcat去管理用戶和商品,而用戶管理和商品管理是兩個單獨的Web應用。

終端客戶通過user.shopping.com域名去搜索商品和下訂單,搜索功能和訂單管理也是兩個獨立的Web應用。

針對這樣的部署,Tomcat會創建一個Service組件和一個Engine容器組件,

在Engine容器下創建兩個Host子容器,在每個Host容器下創建兩個Context子容器。由於一個Web應用通常有多個Servlet,Tomcat還會在每個Context容器裏創建多個Wrapper子容器。每個容器都有對應的訪問路徑,你可以通過下面這張圖來幫助你理解。

假如有用戶訪問一個URL,比如圖中的http://user.shopping.com:8080/order/buy,Tomcat如何將這個URL定位到一個Servlet呢?

首先,根據協議和端口號選定Service和Engine。

我們知道Tomcat的每個連接器都監聽不同的端口,比如Tomcat默認的HTTP連接器監聽8080端口、默認的AJP連接器監聽8009端口。上面例子中的URL訪問的是8080端口,因此這個請求會被HTTP連接器接收,而一個連接器是屬於一個Service組件的,這樣Service組件就確定了。我們還知道一個Service組件裏除了有多個連接器,還有一個容器組件,具體來說就是一個Engine容器,因此Service確定了也就意味着Engine也確定了。

然後,根據域名選定Host。

Service和Engine確定後,Mapper組件通過URL中的域名去查找相應的Host容器,比如例子中的URL訪問的域名是user.shopping.com,因此Mapper會找到Host2這個容器。

之後,根據URL路徑找到Context組件。

Host確定以後,Mapper根據URL的路徑來匹配相應的Web應用的路徑,比如例子中訪問的是/order,因此找到了Context4這個Context容器。

最後,根據URL路徑找到Wrapper(Servlet)。

Context確定後,Mapper再根據web.xml中配置的Servlet映射路徑來找到具體的Wrapper和Servlet

參考

servlet的本質是什麼,它是如何工作的?

特別說明:本文圖片,均引自極客時間。

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