1. 程式人生 > >Tomcat(三):tomcat處理連接的詳細過程

Tomcat(三):tomcat處理連接的詳細過程

field rest web.xml sco gpo appear neu pragma 再次

本文目錄:
1. Tomcat組件體系結構
2. Tomcat和httpd/nginx在監聽和處理請求上的區別
  2.1 Tomcat如何處理並發請求
3. Tomcat處理jsp動態資源的過程
4. Tomcat處理靜態資源的過程

tomcat可以處理靜態資源的請求,也可以通過servlet處理動態資源的請求。處理jsp動態資源時,先通過jasper組件(具體的是JspServlet)將jsp翻譯成java源代碼並編譯成class後運行。需要知道的是,靜態資源也一樣是通過servlet處理的,只不過它使用的servlet是定義在$catalina_home/conf/web.xml中默認的servlet。本文將詳細分析tomcat如何處理客戶端請求(並發)以及如何處理動、靜態資源。

1.Tomcat組件體系結構

如下兩圖:上面的圖是tomcat組件體系的簡圖,下面的圖是Service組件細化後的圖。

技術分享圖片

技術分享圖片

其中:

  1. server組件是管理tomcat實例的組件,可以監聽一個端口,從此端口上可以遠程向該實例發送shutdown關閉命令。
  2. service組件是一個邏輯組件,用於綁定connector和container,有了service表示可以向外提供服務,就像是一般的daemon類服務的service。可以認為一個service就啟動一個JVM,更嚴格地說,一個engine組件才對應一個JVM(定義負載均衡時,jvmRoute就定義在Engine組件上用來標識這個JVM),只不過connector也工作在JVM中。
  3. connector組件是監聽組件,它有四個作用:
    • (1).開啟監聽套接字,監聽外界請求,並建立TCP連接;
    • (2).使用protocolHandler解析請求中的協議和端口等信息,如http協議、AJP協議;
    • (3).根據解析到的信息,使用processer將請求數據轉發給綁定的Engine;
    • (4).接收響應數據並返回給客戶端。
  4. container是容器,它是一類組件,在配置文件(如server.xml)中沒有體現出來。它包含4個容器類組件:engine容器、host容器、context容器和wrapper容器。
  5. engine容器用於從connector組件處接收轉發過來的請求,然後按照分析的結果將相關參數傳遞給匹配出的虛擬主機。engine還用於指定默認的虛擬主機。
  6. host容器定義虛擬主機,由於tomcat主要是作為servlet容器的,所以為每個webapp指定了它們的根目錄appBase。
  7. context容器主要是根據path和docBase獲取一些信息,將結果交給其內的wrapper組件進行處理(它提供wrapper運行的環境,所以它叫上下文context)。一般來說,都采用默認的標準wrapper類,因此在context容器中幾乎不會出現wrapper組件。
  8. wrapper容器對應servlet的處理過程。它開啟servlet的生命周期,根據context給出的信息以及解析web.xml中的映射關系,負責裝載相關的類,初始化servlet對象init()、執行servlet代碼service()以及服務結束時servlet對象的銷毀destory()。
  9. executor組件為每個Service組件提供線程池,使得Engine可以從線程池中獲取線程處理請求,從而實現tomcat的並發處理能力。一定要註意,Executor的線程池大小是為Engine組件設置,而不是為Connector設置的,Connector的線程數量由Connector組件的acceptorThreadCount屬性來設置。如果要在配置文件中設置該組件,則必須設置在Connector組件的前面,以便在Connector組件中使用executor屬性來引用配置好的Executor組件。如果不顯式設置,則采用Connector組件上的默認配置,默認配置如下:
    • (1).maxThreads:最大線程數,默認值200。
    • (2).minSpareThreads:最小空閑線程數,默認值25。
    • (3).maxIdleTime:空閑線程的線程空閑多長時間才會銷毀,默認值60000即1分鐘。
    • (4).prestartminSpareThreads:是否啟動executor時就直接創建等於最小空閑線程數的線程,默認值為false,即只在有連接請求進入時才會創建。

根據上面描述的tomcat組件體系結構,處理請求的大致過程其實很容易推導出來:

Client(request)-->Connector-->Engine-->Host-->Context-->Wrapper(response)-->Connector-->Client

2.Tomcat和httpd/nginx在監聽和處理請求上的區別

在監聽和處理請求上,tomcat和httpd/nginx等服務程序不一樣,而且是巨大的區別。因此,在理解處理請求時,萬萬不可將httpd/nginx的處理模式套在tomcat上。

關於httpd/nginx等服務程序處理連接時的過程,此處僅簡單說明以體現它們和tomcat的不同之處,詳細內容可參見我另一篇文章:不可不知的socket和TCP連接過程

(1).httpd/nginx等都是監聽進程/線程負責監聽,當監聽到連接請求時,將生成一個新的已連接套接字放進一個稱為已連接隊列中,然後監聽進程/線程繼續回去監聽。而負責處理請求的共作進程/線程則從該隊列中獲取已連接套接字並與客戶端建立TCP連接,然後與客戶端進行通信,包括接收客戶端的資源請求數據、構建和響應數據給客戶端。

(2).tomcat雖然也將監聽和處理請求的工作分別使用不同的組件進行處理,但connector線程監聽到請求就直接建立TCP連接,並一直與客戶端保持該連接。connector線程會分析請求並將結果轉發給與之綁定的Engine組件,Engine線程負責處理請求以及構建響應數據,但Engine組件不會和客戶端建立任何連接。Engine的一切數據來源都是Connector,客戶端任何一次資源請求都會發送到connector上,並從connector轉發給Engine。Engine構建響應後,再次將響應數據轉發給Connector,並由Connector做一些處理(如加上首部字段)回復給客戶端。

只要明確一點即可推導出tomcat的連接和請求處理機制:任何一次從外界流入的請求都必將經過connector,任何一次從本地流出的響應數據也都必將經過connector。這正是連接器的意義所在──連接客戶端和服務端servlet。

2.1 tomcat如何處理並發請求

connector組件支持4種IO協議類型:同步阻塞BIO、同步非阻塞NIO、異步非阻塞NIO2、apache基金會提供的IO模型APR(IO模型只是APR類庫的其中一種功能模塊)。它們的區別如下圖所示,除了BIO,其他IO模型在接受新請求上都是非阻塞的,因此這裏不考慮BIO,而且現在也不會有人將connector設置成BIO模式。

技術分享圖片

該表中,最需要關註的是"Wait for next Request"行,NIO/NIO2/APR都是Non Blocking,這表示正在處理某個請求時不會被阻塞,可以接收額外的請求,這是tomcat實現並發處理請求的關鍵。

再來看connector組件和並發數量有關的設置選項:

acceptorThreadCount:用於接收連接請求的線程數。默認值為1。多核CPU系統應該增大該值,另外由於長連接的存在,也應該考慮增大該值。
maxThreads:線程池中最多允許存在多少線程用於處理請求。默認值為200。它是最大並發處理的數量,但不影響接收線程接收更多的連接。
maxConnections:服務端允許接收和處理的最大連接數。當達到該值後,操作系統還能繼續接收額外acceptCount個的連接請求,但這些連接暫時不會被處理。當Connector類型為BIO模型時的默認值等於maxThread的值,當為NIO/NIO2模型時的默認值為10000,當APR時默認長度為8192。
acceptCount:當所有請求處理線程都處於忙碌狀態時,連接請求將進入等待隊列,該值設置等待隊列的長度。當達到隊列最大值後,如果還有新連接請求進入,則會被拒絕。默認隊列長度為100。

從上面幾個屬性的意義來分析並發機制:

  • (1).connector中最多有acceptorThreadCount個專門負責監聽、接收連接請求並建立TCP連接的線程,這些線程是非阻塞的(不考慮BIO)。當和某客戶端建立TCP連接後,可以繼續去監聽或者將Engine返回的數據發送給客戶端或者處理其它事情。
  • (2).線程池中的最大線程數maxThreads決定了某一刻允許處理的最大並發請求數,這是專門負責處理connector轉發過來的請求的線程,可以認為這些線程專門是為Engine組件服務的(因此我將其稱之為Engine線程)。註意,maxThreads決定的是某一刻的最大並發處理能力,但不意味著maxThreads數量的線程只能處理maxThreads數量的請求,因為這些Engine線程也是非阻塞的,當處理某個請求時出現IO等待時,它不會阻塞,而是繼續處理其它請求。也就是說,每個請求都占用一個Engine線程直到該客戶端的所有請求處理完畢,但每個Engine線程可以處理多個請求。同時還能推測出,每個connector線程可以和多個Engine線程綁定(connector線程的數量遠少於Engine線程的數量)。
  • (3).當並發請求數量逐漸增多,tomcat處理能力的極限由maxConnector決定,這個值是由maxThreads和acceptorThreadCount以及非阻塞特性同時決定的。由於非阻塞特性,無論是connector線程還是Engine線程,都能不斷接收、處理新請求。它的默認值看上去很大(10000或8192),但分配到每個線程上的數量並不大。假設不考慮監聽線程對數量的影響,僅從處理線程上來看,10000個連接分配給200個處理線程,每個處理線程可以輪詢處理50個請求。和nginx默認的一個worker線程允許1024個連接相比,已經很少了,當然,因為架構模型不一樣,它們沒有可比性。
  • (4).當並發請求數量繼續增大,tomcat還能繼續接收acceptCount個請求,但不會去建立連接,所以也不會去處理。實際上,這些請求不是tomcat接收的,而是操作系統接收的,接收後放入到由Connector創建的隊列中,當tomcat有線程可以處理新的請求了再去隊列中取出並處理。

再來細分一下tomcat和httpd/nginx的不同點:

  • (1).httpd/nginx的監聽者只負責監聽和產生已連接套接字,不會和客戶端直接建立TCP連接。而tomcat的監聽者connector線程不僅會監聽,還會直接建立TCP連接,且一直處於ESTABLISHED狀態直到close
  • (2).httpd/nginx的工作進程/線程首先從已連接套接字隊列中獲取已連接套接字,並與客戶端建立TCP連接,然後和客戶端通信兵處理請求、響應數據。而tomcat的工作線程(Engine線程)只接受來自connector轉發過來的請求,處理完畢後還會將響應數據轉發回connector線程,由connector將響應數據傳輸給客戶端(和客戶端的所有通信數據都必須經過連接器connector來傳輸)
  • (3).不難推斷出,一個Connector線程可以和多個客戶端建立TCP連接,也可以和多個Engine線程建立綁定關系,而一個Engine線程可以處理多個請求。如果不理解並發處理機制,這一點很容易被"Connector組件和Engine組件綁定在一起組成Service組件"這句話誤導。這句話的意思並不是要求它們1:1對應,就像httpd/nginx也一樣,一個監聽者可能對應多個工作者。

因此,tomcat處理連接的過程如下圖所示,其中我把Engine線程處理請求的過程用"Engine+N"來表示,例如Engine線程1下的Engine1表示該Engine線程處理的某個請求,Engine2表示該線程處理的另一個請求。

技術分享圖片

3.Tomcat處理jsp動態資源的過程

假設tomcat的配置如下,其中項目名稱為"xiaofang"。

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

<Engine name="Catalina" defaultHost="localhost">
    <Host name="www.xiaofang.com"  appBase="webapps/xiaofang"
          unpackWARs="true" autoDeploy="true">
        <Context path="" docBase="" reloadable="true" />
        <Context path="/xuexi" docBase="xuexi" reloadable="true" />
    </Host>

    <Host name="localhost"  appBase="webapps"
          unpackWARs="true" autoDeploy="true">
    </Host>
</Engine>

當客戶端訪問http://www.xiaofang.com:8080/xuexi/abc.jsp時,其請求的是$CATALINA_HOME/webapps/xiaofang/xuexi/abc.jsp文件。

(1).Connector組件扮演的角色。

Connector組件首先監聽到該請求,於是建立TCP連接,並分析該請求。Connector分析請求的內容包括請求的協議、端口、參數等。因為這裏沒考慮集群問題,因此只可能是http協議而不可能是ajp協議的請求。分析後,將請求和相關參數轉發給關聯的Engine組件進行處理。

(2).Engine組件扮演的角色。

Engine組件主要用於將請求分配到匹配成功的虛擬主機上,如果沒有能匹配成功的,則分配到默認虛擬主機上。對於上面的請求,很顯然將分配到虛擬主機www.xiaofang.com上。

(3).Host組件扮演的角色。

Host組件收到Engine傳遞過來的請求參數後,將對請求中的uri與Context中的path進行匹配,如果和某個Context匹配成功,則將請求交給該Context處理。如果匹配失敗,則交給path=""對應的Context來處理。所以,根據匹配結果,上面的請求將交給<Context path="/xuexi" docBase="xuexi" />進行處理。

註意,這次的uri匹配是根據path進行的匹配,它是目錄匹配,不是文件匹配也就是說,只匹配到uri中的xuexi就結束匹配。之所以要明確說明這一點,是因為後面還有一次文件匹配,用於決定交給哪個Servlet來處理。

(4).Context和Wrapper組件扮演的角色。

到了這裏,就算真正到了Servlet程序運行的地方了,相比於前面幾個組件,這裏的過程也更復雜一些。

請求http://www.xiaofang.com:8080/xuexi/abc.jsp經過Host的uri匹配後,分配給<Context path="/xuexi" docBase="xuexi" />進行處理,此時已經匹配了url中的目錄,剩下的是abc.jsp。abc.jsp也需要匹配,但這個匹配是根據web.xml中的配置進行匹配的。

首先,從項目名為xiaofang的私有web.xml中進行查找,即webapps/xiaofang/WEB-INF/web.xml。由於此處僅為簡單測試,因此並沒有該文件。

於是從全局web.xml即$CATALINA_HOME/conf/web.xml中匹配abc.jsp。以下是web.xml中能匹配到該文件名的配置部分。

<!-- The mappings for the JSP servlet -->
<servlet-mapping>
    <servlet-name>jsp</servlet-name>
    <url-pattern>*.jsp</url-pattern>
    <url-pattern>*.jspx</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>jsp</servlet-name>
    <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
</servlet>

首先根據<servlet-mapping>中的url-pattern進行文件匹配,發現該url匹配的是servlet-name為"jsp"的servlet,然後再找到與該名稱對應的<servlet>標簽段,發現處理該動態資源的類為org.apache.jasper.servlet.JspServlet,於是找到該類對應的class文件,該class文件歸檔在$catalina_home/lib/jasper.jar中。

技術分享圖片

JspServlet程序的作用是將jsp文件翻譯成java源代碼文件,並放在$catalina_home/work目錄下。然後將該java源文件進行編譯,編譯後的class文件也放在work目錄下。這個class文件就是abc.jsp最終要執行的servlet小程序。

[root@xuexi ~]# ls /usr/local/tomcat/work/Catalina/www.xiaofang.com/xuexi/org/apache/jsp/
index_jsp.class  index_jsp.java  new_

在翻譯後的servlet小程序中,不僅會輸出業務邏輯所需的數據,還會輸出html/css代碼,這樣一來,客戶端接收到的數據都將是排版好的。

4.Tomcat處理靜態資源的過程

對於tomcat來說,無論是動態還是靜態資源,都是經過servlet處理的。只不過處理靜態資源的servlet是默認的servlet而已。

在$catalina_home/conf/web.xml中關於靜態資源處理的配置如下。

<!-- The mapping for the default servlet -->
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
</servlet>

需要記住的是,web.xml中的url-pattern是文件匹配,而server.xml中的<Context path="URL-PATTERN" />是目錄匹配。

上面web.xml中的<url-pattern>/</url-pattern>表示的是默認servlet。這意味著,當web.xml中沒有servlet-mapping能匹配請求url中的路徑時,將匹配servlet-name,即名為default的servlet。然後找到處理default的類為org.apache.catalina.servlets.DefaultServlet,該類的class文件歸檔在$catalina_home/lib/catalina.jar中。該servlet不像JspServlet會翻譯jsp文件,它只有最基本的作用:原樣輸出請求文件中的內容給客戶端。

例如,根據前面的配置,下面幾個請求都將采用默認servlet進行處理,即當作靜態資源處理。

http://www.xiaofang.com:8080/xuexi/index.html
http://www.xiaofang.com:8080/xuexi/abc.js
http://www.xiaofang.com:8080/xuexi/index
http://www.xiaofang.com:8080/xuexi/index.txt

http://www.xiaofang.com:8080/xuexi則不一定,因為tomcat中默認的index文件包含index.jsp和index.html,而index.jsp排在index.html的前面,只有不存在index.jsp時才請求index.html。

回到Linux系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
回到網站架構系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7576137.html
回到數據庫系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7586194.html
轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/8408670.html

註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!

Tomcat(三):tomcat處理連接的詳細過程