Tomcat 9 源碼解析 -- tomcat框架設計

Tomcat整體架構與重要組件

1.Tomcat整體架構

如果你瀏覽過Tomcat源碼相關文章,你一定對此圖不陌生.

Tomcat即是一個HTTP服務器,也是一個servlet容器,主要目的就是包裝servlet,並對請求響應相應的servlet,純servlet的web應用似乎很好理解Tomcat是如何裝載servlet的,但,當使用一些MVC框架時,如spring MVC、strusts2,可能就找不出servlet在哪裏?其實spring MVC框架就是一整個servlet,在web.xml中配置如下:

<!-- Spring MVC servlet -->
	<servlet>
		<servlet-name>SpringMVC</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:spring-mvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
		<async-supported>true</async-supported>
	</servlet>
	<servlet-mapping>
		<servlet-name>SpringMVC</servlet-name>
		<!-- 此處可以可以配置成*.do,對應struts的後綴習慣 -->
		<url-pattern>/</url-pattern>
	</servlet-mapping>

他的總體結構用下圖來表示。

如上圖所示,tomcat由Server、Service、Engine、Connerctor、Host、Context組件組成,

其中帶有s的代表在一個tomcat實例上可以存在多個組件,比如Context(s),

tomcat允許我們部署多個應用,每個應用對應一個Context。

這些組件在tomcat的conf/server.xml文件中可以找到,對tomcat的調優需要改動該文件
 

server.xml
<Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
           connectionTimeout="20000"
           redirectPort="8443" />

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />

    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

    <Engine name="Catalina" defaultHost="localhost">
      <Realm className="org.apache.catalina.realm.LockOutRealm">
        <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>
      </Realm>
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log" suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
      </Host>
    </Engine>
</Service>

 

更細緻一點的圖形表示:

 

1. Tomcat主要有兩個組件,連接器【Connector】和容器【Container】,所謂連接器就是一個http請求過來了,連接器負責接收這個請求,然後轉發給容器。容器即servlet容器,容器有很多層,分別是Engine,

    Host,Context,Wrapper。最大的容器Engine,代表一個servlet引擎,接下來是Host,代表一個虛擬機,然後是Context,代表一個應用,Wrapper對應一個servlet。從連接器

    傳過來連接後,容器便會順序經過上面的容器,最後到達特定的servlet。要說明的是Engine,Host兩種容器在不是必須的。實際上一個簡單的tomcat只要連接器和容器就可以了,

    但tomcat的實現爲了統一管理連接器和容器等組件,額外添加了服務器組件(server)和服務組件(service),添加這兩個東西的原因我個人覺得就是爲了方便統一管理連接器和

    容器等各種組件。一個server可以有多個service,一個service包含多個連接器和一個容器,當然還有一些其他的東西,看下面的圖就很容易理解Tomcat的架構了:

  1.  

圖一: 

  1. Server掌管着整個Tomcat的生死大權;
  2. Service 是對外提供服務的;
  3. Connector用於接受請求並將請求封裝成Request和Response來具體處理;
  4. Container用於封裝和管理Servlet,以及具體處理request請求。

Tomcat中最頂層的容器是Server,代表着整個服務器,從上圖中可以看出,一個Server可以包含至少一個Service,用於具體提供服務。

Service主要包含兩個部分:Connector和Container。從上圖中可以看出 Tomcat 的心臟就是這兩個組件,它們的作用如下: 

Connector

由上面的圖我們可以看出來一個Service服務可以包含多個Connector,實際上Tomcat中包含了3個Connector,分別對應HTTP請求,HTTPS請求以及AJP請求。那麼Connector在Tomcat中到底扮演了什麼樣的角色呢?Tomcat中的Connector負責的任務就是接受請求,創建request和response,解析請求中數據,將數據封裝到request中,最後將request和response作爲參數傳遞給Container對象!

由於不同協議規則不同,解析起來也是不一樣的,所以需要多個Connector。

Container

(ContainerBase、StandardServer、StandardService、WebappLoader、Connector、StandardContext、StandardEngine、StandardHost、StandardWrapper等容器都繼承了LifecycleMBeanBase,因此這些容器都具有了同樣的生命週期並可以通過JMX進行管理)

Container實際上只是容器的概稱。而Tomcat中包含有多個Container。那麼Container的作用是什麼呢?

Container的作用就是接收Connector傳遞的request和response,對request和response進行了一些過濾封裝,同時調用對應的Servlet去處理該request,最後將處理完成後的結果封裝到response中返回給客戶端!

每個Container從大到小分別爲Engine-->Host-->Context-->Wrapper

  1. Engine容器代表整個Catalina servlet引擎
  2. Host容器表示一個虛擬主機,Tomcat自帶的是一個叫localhost的主機
  3. Context容器表示一個WEB應用程序
  4. Wrapper表示一個獨立的Servlet

對於一個Engine可以包含多個Host,Tomcat自帶了一個叫做localhost的Host容器,一個Host容器可以包含多個Context,一個Context可以包含多個Wrapper。

是不是感覺這幾個單詞很熟悉,是的!在Tomcat中我們可以使用Tomcat默認提供的這些容器,也可以在CATALINA_HOME/conf下的server.xml中配置,而這幾個單詞正是對應server.xml中的各個配置選項的名稱!

 

Connector和Container的微妙關係

一個請求發送到Tomcat之後,首先經過Service然後會交給我們的Connector,Connector用於接收請求,並將接收的請求封裝爲Request和Response來具體處理。Request和Response封裝完之後再交由Container進行處理,Container處理完請求之後再返回給Connector,最後在由Connector通過Socket將處理的結果返回給客戶端,這樣整個請求的就處理完了!

Connector最底層使用的是Socket來進行連接的,Request和Response是按照HTTP協議來封裝的,所以Connector同時需要實現TCP/IP協議和HTTP協議。

Tomcat既然處理請求,那麼肯定需要先接收到這個請求,而想要接收請求這個東西,我們首先就需要看一下Connector。

Connector架構分析

connector類繼承關係: 

 

 Http11NioProtocol類繼承關係:

 

 

Endpont的繼承關係 以及 類中定義的屬性    :

NioEndpoint
這個NioEndpoint作爲Tomcat NIO的IO處理策略,主要提供工作線程和線程池:
   Socket接收者Acceptor
   Socket輪詢者Poller
   工作線程池

主要包含 LimitLatch、Acceptor、Poller、SocketProcessor、Excutor  5個部分:
   LimitLatch 是一個連接控制器,負責連接限制,nio模式下默認10000,達到閾值則拒絕連接請求。 (AbstractEndpoint 類中的屬性 connectionLimitLatch )
      當一個請求進入到Tomcat的時候,就會調用 AbstractEndpoint # countUpOrAwaitConnectio()
      
   Acceptor 負責接收請求,默認由1個線程負責,將請求的事件註冊到事件列表中。
   Poller 負責輪詢上述產生的事件,將就緒的事件生成  SokcetProcessor  ,交給Excutor去執行。
   SocketProcessor 裏面的doRun方法,封裝了Socket的讀寫,完成Container調用邏輯。
   
看到 Endpoint 的屬性中出現了很多 SynchronizedStack ,這個數據結構爲Tomcat量身定做,是ConcurrentLinkedQueue一個GC-free的輕量級替代方案,
提供擴容方案,最大128,但沒有提供減少容量的方法。減少容量必然帶來數組對象的回收,適用於數據量比較固定的場景,另外這個數據結構本身由數組維護,
減少了維護節點的開銷。
而另一個 SynchronizedQueue 則是一個GC-free的容器,只不過這個是一個FIFO無界容器。

 

 

Connector用於接受請求並將請求封裝成Request和Response,然後交給Container進行處理,Container處理完之後在交給Connector返回給客戶端。因此,我們可以把Connector分爲四個方面進行理解:

首先看一下Connector的結構圖

圖二: 

Connector就是使用ProtocolHandler來處理請求的,不同的ProtocolHandler代表不同的連接類型,比如:Http11Protocol使用的是普通Socket來連接的,Http11NioProtocol使用的是NioSocket來連接的。

其中ProtocolHandler由包含了三個部件:Endpoint、Processor、Adapter。

Endpoint用來處理底層Socket的網絡連接,Processor用於將Endpoint接收到的Socket封裝成Request,Adapter用於將Request交給Container進行具體的處理。

Endpoint由於是處理底層的Socket網絡連接,因此Endpoint是用來實現TCP/IP協議的,而Processor用來實現HTTP協議的,Adapter將請求適配到Servlet容器進行具體的處理。

Endpoint的抽象實現AbstractEndpoint裏面定義的Acceptor和AsyncTimeout兩個內部類和一個Handler接口。Acceptor用於監聽請求,AsyncTimeout用於檢查異步Request的超時,Handler用於處理接收到的Socket,在內部調用Processor進行處理。

至此,我們應該很輕鬆回答了   1.  2.  3.   的問題,但  4.還是不知道。

接下來我們就來看一下Container是如何進行處理的,以及處理完之後是如何將處理完的結果返回給Connector的

Container架構分析

Container用於封裝和管理Servlet,以及具體處理Request請求,在Connector內部包含了4個子容器,結構圖如下

圖三: 

 

4個子容器的作用分別是:

(1)Engine:引擎,用來管理多個站點,一個Service最多只能有一個Engine;

(2)Host:代表一個站點,也可以叫虛擬主機,通過配置Host就可以添加站點;

(3)Context:代表一個應用程序,對應着平時開發的一套程序,或者一個WEB-INF目錄以及下面的web.xml文件;

(4)Wrapper:每一Wrapper封裝着一個Servlet。

Context和Host的區別是Context表示一個應用,我們的Tomcat中默認的配置下webapps下的每一個文件夾目錄都是一個Context,其中ROOT目錄中存放着主應用,其他目錄存放着子應用,而整個webapps就是一個Host站點。

我們訪問應用Context的時候,如果是ROOT下的則直接使用域名就可以訪問,例如:www.gaohaicheng.com,如果是Host(webapps)下的其他應用,則可以使用www.gaohaicheng.com/docs進行訪問。當然默認指定的根應用(ROOT)是可以進行設定的,只不過Host站點下默認的主營用是ROOT目錄下的。

看到這裏我們知道Container是什麼,但還是不知道Container是如何進行處理的,以及處理完之後是如何將處理完的結果返回給Connector的。

 

Container如何處理請求的

Container處理請求是使用Pipeline-Valve管道來處理的!(Valve是閥門之意)

Pipeline-Valve是責任鏈模式,責任鏈模式是指在一個請求處理的過程中有很多處理者依次對請求進行處理,每個處理者負責做自己相應的處理,處理完之後將處理後的請求返回,再讓下一個處理着繼續處理。

但是,Pipeline-Valve使用的責任鏈模式和普通的責任鏈模式有些不同,區別主要有以下兩點:

(1)每個Pipeline都有特定的Valve,而且是在管道的最後一個執行,這個Valve叫做BaseValve,BaseValve是不可刪除的;

(2)在上層容器的管道的BaseValve中會調用下層容器的管道。

我們知道Container包含四個子容器,而這四個子容器對應的BaseValve分別在:

StandardEngineValve

StandardHostValve

StandardContextValve

StandardWrapperValve

Pipeline的處理流程圖如下

圖四: 

 

(1)Connector在接收到請求後會首先調用最頂層容器的Pipeline來處理,這裏的最頂層容器的Pipeline就是EnginePipeline(Engine的管道)。

(2)在Engine的管道中依次會執行EngineValve1、EngineValve2等等,最後會執行StandardEngineValve,在StandardEngineValve中會調用Host管道,然後再依次執行Host的HostValve1、HostValve2等,最後在執行StandardHostValve,然後再依次調用Context的管道和Wrapper的管道,最後執行到StandardWrapperValve。

(3)當執行到StandardWrapperValve的時候,會在StandardWrapperValve中創建FilterChain,並調用其doFilter方法來處理請求,這個FilterChain包含着我們配置的與請求相匹配的Filter和Servlet,其doFilter方法會依次調用所有的Filter的doFilter方法和Servlet的service方法,這樣請求就得到了處理!

(4)當所有的Pipeline-Valve都執行完之後,並且處理完了具體的請求,這個時候就可以將返回的結果交給Connector了,Connector在通過Socket的方式將結果返回給客戶端。

總結

至此,我們已經對Tomcat的整體架構有了大致的瞭解,從   圖一、二、三、四    可以看出來每一個組件的基本要素和作用,各位讀者在腦海裏應該有一個大概的輪廓了。如果在面試的時候,能夠簡單地聊一下Tomcat,將上面的內容脫口而出,想必面試官一定會對你刮目相看的!

 

 

 

 

 

 

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