本文目錄:1. Tomcat組件體繫結構2. Tomcat和httpd/nginx在監聽和處理請求上的區別 2.1 Tomcat如何處理併發請求3. Tomcat處理jsp動態資源的過程4. Tomcat處理靜態資源的過程 tomcat可以處理靜態資源的請求,也可以通過servlet處理動態資源的請 ...
本文目錄:
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組件細化後的圖。
其中:
server
組件是管理tomcat實例的組件,可以監聽一個埠,從此埠上可以遠程向該實例發送shutdown關閉命令。service
組件是一個邏輯組件,用於綁定connector和container,有了service表示可以向外提供服務,就像是一般的daemon類服務的service。可以認為一個service就啟動一個JVM,更嚴格地說,一個engine組件才對應一個JVM(定義負載均衡時,jvmRoute就定義在Engine組件上用來標識這個JVM),只不過connector也工作在JVM中。connector
組件是監聽組件,它有四個作用:- (1).開啟監聽套接字,監聽外界請求,並建立TCP連接;
- (2).使用protocolHandler解析請求中的協議和埠等信息,如http協議、AJP協議;
- (3).根據解析到的信息,使用processer將請求數據轉發給綁定的Engine;
- (4).接收響應數據並返回給客戶端。
container
是容器,它是一類組件,在配置文件(如server.xml)中沒有體現出來。它包含4個容器類組件:engine容器、host容器、context容器和wrapper容器。engine
容器用於從connector組件處接收轉發過來的請求,然後按照分析的結果將相關參數傳遞給匹配出的虛擬主機。engine還用於指定預設的虛擬主機。host
容器定義虛擬主機,由於tomcat主要是作為servlet容器的,所以為每個webapp指定了它們的根目錄appBase。context
容器主要是根據path和docBase獲取一些信息,將結果交給其內的wrapper組件進行處理(它提供wrapper運行的環境,所以它叫上下文context)。一般來說,都採用預設的標準wrapper類,因此在context容器中幾乎不會出現wrapper組件。wrapper
容器對應servlet的處理過程。它開啟servlet的生命周期,根據context給出的信息以及解析web.xml中的映射關係,負責裝載相關的類,初始化servlet對象init()、執行servlet代碼service()以及服務結束時servlet對象的銷毀destory()。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 data)-->Connector(response header)-->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
註:若您覺得這篇文章還不錯請點擊右下角推薦,您的支持能激發作者更大的寫作熱情,非常感謝!