概述
Tomcat是一個JSP/Servlet容器。其作為Servlet容器,有三種工作模式:獨立的Servlet容器、進程內的Servlet容器和進程外的Servlet容器。
Tomcat運行可以選擇BIO或者NIO模型,原理分別對應上面的3和4兩種方式。Tomcat默認是BIO方式運行,如果想要換成NIO,可以配置server.xml:
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" .../>
Tomcat目錄
tomcat
|---bin:存放啟動和關閉tomcat腳本
|---conf:存放不同的配置文件(server.xml和web.xml);
|---doc:存放Tomcat文檔;
|---lib/japser/common:存放Tomcat運行需要的庫文件(JARS);
|---logs:存放Tomcat執行時的LOG文件;
|---src:存放Tomcat的源代碼;
|---webapps:Tomcat的主要Web發布目錄(包括應用程序示例);
|---work:存放jsp編譯后產生的class文件;
Tomcat配置文件
打開con文件夾可以看到Tomcat的配置文件:
- server.xml: Tomcat的主配置文件,包含Service, Connector, Engine, Realm, Valve, Hosts主組件的相關配置信息;
- web.xml:遵循Servlet規范標準的配置文件,用于配置servlet,并為所有的Web應用程序提供包括MIME映射等默認配置信息;
- tomcat-user.xml:Realm認證時用到的相關角色、用戶和密碼等信息;Tomcat自帶的manager默認情況下會用到此文件;在Tomcat中添加/刪除用戶,為用戶 指定角色等將通過編輯此文件實現;
- catalina.policy:Java相關的安全策略配置文件,在系統資源級別上提供訪問控制的能力;
- catalina.properties:Tomcat內部package的定義及訪問相關控制,也包括對通過類裝載器裝載的內容的控制;Tomcat在啟動時會事先讀取此文件的相關設置;
- logging.properties: Tomcat6通過自己內部實現的JAVA日志記錄器來記錄操作相關的日志,此文件即為日志記錄器相關的配置信息,可以用來定義日志記錄的組 件級別以及日志文件的存在位置等;
- context.xml:所有host的默認配置信息;
Tomcat架構及常用的組件
1、Server組件
如上面示例文件中定義的:
<Server port=”8005” shutdown=”SHUTDOWN”>
這會讓Tomcat6啟動一個server實例(即一個JVM),它監聽在8005端口以接收shutdown命令,使用 telnet 連接8005 端口可以直接執行 SHUTDOWN 命令來關閉 Tomcat。各Server的定義不能使用同一個端口,這意味著如果在同一個物理機上啟動了多個Server實例,必須配置它們使用不同的端口。這個端口的定義用于為管理員提供一個關閉此實例的便捷途徑,因此,管理員可以直接telnet至此端口使用SHUTDOWN命令關閉此實例。不過,基于安全角度的考慮,這通常不允許遠程進行。
Server的相關屬性:
className: 用于實現此Server容器的完全限定類的名稱,默認為org.apache.catalina.core.StandardServer;
port: 接收shutdown指令的端口,默認僅允許通過本機訪問,默認為8005;
shutdown:發往此Server用于實現關閉tomcat實例的命令字符串,默認為SHUTDOWN;
2、Service組件:
Service主要用于關聯一個引擎和與此引擎相關的連接器,每個連接器通過一個特定的端口和協議接收入站請求交將其轉發至關聯的引擎進行處理。困此,Service要包含一個引擎、一個或多個連接器。
如上面示例中的定義:
<Service name=”Catalina”>
這定義了一個名為Catalina的Service,此名字也會在產生相關的日志信息時記錄在日志文件當中。
Service相關的屬性:
className: 用于實現service的類名,一般都是org.apache.catalina.core.StandardService。
name:此服務的名稱,默認為Catalina;
3、Connector組件:
進入Tomcat的請求可以根據Tomcat的工作模式分為如下兩類:
Tomcat作為應用程序服務器:請求來自于前端的web服務器,這可能是Apache, IIS, Nginx等;
Tomcat作為獨立服務器:請求來自于web瀏覽器;
Tomcat應該考慮工作情形并為相應情形下的請求分別定義好需要的連接器才能正確接收來自于客戶端的請求。一個引擎可以有一個或多個連接器,以適應多種請求方式。
定義連接器可以使用多種屬性,有些屬性也只適用于某特定的連接器類型。一般說來,常見于server.xml中的連接器類型通常有4種:
- HTTP連接器 2) SSL連接器 3) AJP 1.3連接器 4) proxy連接器
如上面示例server.xml中定義的HTTP連接器:
<Connector port=”8080″ protocol=”HTTP/1.1″maxThreads=”150″ connectionTimeout=”20000″redirectPort=”8443″/>
定義連接器時可以配置的屬性非常多,但通常定義HTTP連接器時必須定義的屬性只有“port“,定義AJP連接器時必須定義的屬性只有”protocol”,因為默認的協議為HTTP。以下為常用屬性的說明:
1) address:指定連接器監聽的地址,默認為所有地址,即0.0.0.0; 可以自己指定地,如
2) maxThreads:支持的最大并發連接數,默認為200;
3) port:監聽的端口,默認為0;
4) protocol:連接器使用的協議,默認為HTTP/1.1,定義AJP協議時通常為AJP/1.3;
5) redirectPort:如果某連接器支持的協議是HTTP,當接收客戶端發來的HTTPS請求時,則轉發至此屬性定義的端口;
6) connectionTimeout:等待客戶端發送請求的超時時間,單位為毫秒,默認為60000,即1分鐘;
7) enableLookups:是否通過request.getRemoteHost()進行DNS查詢以獲取客戶端的主機名;默認為true; 進行反解的,可以設置為false
8) acceptCount:設置等待隊列的最大長度;通常在tomcat所有處理線程均處于繁忙狀態時,新發來的請求將被放置于等待隊列中;
下面是一個定義了多個屬性的SSL連接器:
<Connector port=”8443″
maxThreads=”150″ minSpareThreads=”25″ maxSpareThreads=”75″
enableLookups=”false” acceptCount=”100″ debug=”0″ scheme=”https” secure=”true”
clientAuth=”false” sslProtocol=”TLS” />
4、Engine組件:
Engine是Servlet處理器的一個實例,即servlet引擎,默認為定義在server.xml中的Catalina。Engine需要defaultHost屬性來為其定義一個接收所有發往非明確定義虛擬主機的請求的host組件。如前面示例中定義的:
<Engine name=”Catalina” defaultHost=”localhost”>
常用的屬性定義:
defaultHost:Tomcat支持基于FQDN的虛擬主機,這些虛擬主機可以通過在Engine容器中定義多個不同的Host組件來實現;但如果此引擎的連接器收到一個發往非非明確定義虛擬主機的請求時則需要將此請求發往一個默認的虛擬主機進行處理,因此,在Engine中定義的多個虛擬主機的主機名稱中至少要有一個跟defaultHost定義的主機名稱同名;
name:Engine組件的名稱,用于日志和錯誤信息記錄時區別不同的引擎;
Engine容器中可以包含Realm、Host、Listener和Valve子容器。
5、Host組件:
位于Engine容器中用于接收請求并進行相應處理的主機或虛擬主機,如前面示例中的定義:
<Host name=”localhost” appBase=”webapps”
unpackWARs=”true” autoDeploy=”true”
xmlValidation=”false” xmlNamespaceAware=”false”>
</Host>
常用屬性說明:
1) appBase:此Host的webapps目錄,即存放非歸檔的web應用程序的目錄或歸檔后的WAR文件的目錄路徑;可以使用基于$CATALINA_HOME的相對路徑;
2) autoDeploy:在Tomcat處于運行狀態時放置于appBase目錄中的應用程序文件是否自動進行deploy;默認為true;
3) unpackWars:在啟用此webapps時是否對WAR格式的歸檔文件先進行展開;默認為true;
虛擬主機定義示例:
<Engine name=”Catalina” defaultHost=”localhost”>
<Host name=”localhost” appBase=”webapps”>
<Context path=”” docBase=”ROOT”/>
<Context path=”/bbs” docBase=”/web/bss” #path路徑是定義在defaultHost背后的
reloadable=”true” crossContext=”true”/>
</Host>
<Host name=”mail.magedu.com” appBase=”/web/mail”>
<Context path=”” docBase=”ROOT”/>
</Host>
</Engine>
主機別名定義:
如果一個主機有兩個或兩個以上的主機名,額外的名稱均可以以別名的形式進行定義,如下:
<Host name=”www.ttlsa.com” appBase=”webapps” unpackWARs=”true”>
<Alias>feiyu.com</Alias>
</Host>
6、Context組件:
Context在某些意義上類似于apache中的路徑別名,一個Context定義用于標識tomcat實例中的一個Web應用程序;如下面的定義:
<!– Tomcat Root Context –>
<Context path=”” docBase=”/web/webapps”/>
<!– buzzin webapp –>
<Context path=”/bbs”
docBase=”/web/threads/bbs”
reloadable=”true”>
</Context>
<!– chat server –>
<Context path=”/chat” docBase=”/web/chat”/>
<!– darian web –>
<Context path=”/darian” docBase=”darian”/>
在Tomcat6中,每一個context定義也可以使用一個單獨的XML文件進行,其文件的目錄為$CATALINA_HOME/conf//。可以用于Context中的XML元素有Loader,Manager,Realm,Resources和WatchedResource。
常用的屬性定義有:
docBase:相應的Web應用程序的存放位置;也可以使用相對路徑,起始路徑為此Context所屬Host中appBase定義的路徑;切記,docBase的路徑名不能與相應的Host中appBase中定義的路徑名有包含關系,比如,如果appBase為deploy,而docBase絕不能為deploy-bbs類的名字;
2) path:相對于Web服務器根路徑而言的URI;如果為空“”,則表示為此webapp的根路徑;如果context定義在一個單獨的xml文件中,此屬性不需要定義,有可能是別名;
3) reloadable:是否允許重新加載此context相關的Web應用程序的類;默認為false;
7、Realm組件:
一個Realm表示一個安全上下文,它是一個授權訪問某個給定Context的用戶列表和某用戶所允許切換的角色相關定義的列表。因此,Realm就像是一個用戶和組相關的數據庫。定義Realm時惟一必須要提供的屬性是classname,它是Realm的多個不同實現,用于表示此Realm認證的用戶及角色等認證信息的存放位置
ASRealm:基于Java Authintication and Authorization Service實現用戶認證;
JDBCRealm:通過JDBC訪問某關系型數據庫表實現用戶認證;
JNDIRealm:基于JNDI使用目錄服務實現認證信息的獲取;
MemoryRealm:查找tomcat-user.xml文件實現用戶信息的獲取;
UserDatabaseRealm:基于UserDatabase文件(通常是tomcat-user.xml)實現用戶認證,它實現是一個完全可更新和持久有效的MemoryRealm,因此能夠跟標準的MemoryRealm兼容;它通過JNDI實現;
8、Valve組件:
Valve類似于過濾器,它可以工作于Engine和Host/Context之間、Host和Context之間以及Context和Web應用程序的某資源之間。一個容器內可以建立多個Valve,而且Valve定義的次序也決定了它們生效的次序。Tomcat6中實現了多種不同的Valve:
essLogValve:訪問日志Valve
ExtendedAccessValve:擴展功能的訪問日志Valve
JDBCAccessLogValve:通過JDBC將訪問日志信息發送到數據庫中;
RequestDumperValve:請求轉儲Valve;
RemoteAddrValve:基于遠程地址的訪問控制;
RemoteHostValve:基于遠程主機名稱的訪問控制;
SemaphoreValve:用于控制Tomcat主機上任何容器上的并發訪問數量;
JvmRouteBinderValve:在配置多個Tomcat為以Apache通過mod_proxy或mod_jk作為前端的集群架構中,當期望停止某節點時,可以通過此Valve將用記請求定向至備用節點;使用此Valve,必須使JvmRouteSessionIDBinderListener;
ReplicationValve:專用于Tomcat集群架構中,可以在某個請求的session信息發生更改時觸發session數據在各節點間進行復制;
SingleSignOn:將兩個或多個需要對用戶進行認證webapp在認證用戶時連接在一起,即一次認證即可訪問所有連接在一起的webapp;
ClusterSingleSingOn:對SingleSignOn的擴展,專用于Tomcat集群當中,需要結合ClusterSingleSignOnListener進行工作;
RemoteHostValve和RemoteAddrValve可以分別用來實現基于主機名稱和基于IP地址的訪問控制,控制本身可以通過allow或deny來進行定義,這有點類似于Apache的訪問控制功能;如下面的Valve則實現了僅允許本機訪問/probe:
<Context path=”/probe” docBase=”probe”>
<Valve className=”org.apache.catalina.valves.RemoteAddrValve”
allow=”127.0.0.1″/>
</Context>
請求過程
1、用戶點擊網頁內容,請求被發送到本機端口8080,被在那里監聽的Coyote HTTP/1.1 Connector獲得。
2、Connector把該請求交給它所在的Service的Engine來處理,并等待Engine的回應。
3、Engine獲得請求localhost/test/index.jsp,匹配所有的虛擬主機Host。
4、Engine匹配到名為localhost的Host(即使匹配不到也把請求交給該Host處理,因為該Host被定義為該Engine的默認主機),名為localhost的Host獲得請求/test/index.jsp,匹配它所擁有的所有的Context。Host匹配到路徑為/test的Context(如果匹配不到就把該請求交給路徑名為“ ”的Context去處理)。
5、path=“/test”的Context獲得請求/index.jsp,在它的mapping table中尋找出對應的Servlet。Context匹配到URL PATTERN為*.jsp的Servlet,對應于JspServlet類。
6、構造HttpServletRequest對象和HttpServletResponse對象,作為參數調用JspServlet的doGet()或doPost().執行業務邏輯、數據存儲等程序。
7、Context把執行完之后的HttpServletResponse對象返回給Host。
8、Host把HttpServletResponse對象返回給Engine。
9、Engine把HttpServletResponse對象返回Connector。
10、Connector把HttpServletResponse對象返回給客戶Browser。
tomcat支持的請求處理方式
Tomcat支持三種接收請求的處理方式:BIO、NIO、APR
- BIO模式:阻塞式I/O操作,表示Tomcat使用的是傳統Java?I/O操作(即Java.io包及其子包)。Tomcat7以下版本默認情況下是以bio模式運行的,由于每個請求都要創建一個線程來處理,線程開銷較大,不能處理高并發的場景,在三種模式中性能也最低。
- NIO模式:是java?SE 1.4及后續版本提供的一種新的I/O操作方式(即java.nio包及其子包)。是一個基于緩沖區、并能提供非阻塞I/O操作的Java API,它擁有比傳統I/O操作(bio)更好的并發運行性能。在tomcat 8之前要讓Tomcat以nio模式來運行比較簡單,只需要在Tomcat安裝目錄/conf/server.xml文件中將如下配置:
<Connector port="8080" protocol="HTTP/1.1"connectionTimeout="20000"redirectPort="8443" />
改為:
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"connectionTimeout="20000"redirectPort="8443" />
Tomcat8以上版本,默認使用的就是NIO模式,不需要額外修改
- apr模式:簡單理解,就是從操作系統級別解決異步IO問題,大幅度的提高服務器的處理和響應性能, 也是Tomcat運行高并發應用的首選模式。
啟用這種模式稍微麻煩一些,需要安裝一些依賴庫。
tomcat的線程模型NioEndpoint
先來簡單回顧下目前一般的NIO服務器端的大致實現,借鑒infoq上的一篇文章Netty系列之Netty線程模型中的一張圖
- 一個或多個Acceptor線程,每個線程都有自己的Selector,Acceptor只負責accept新的連接,一旦連接建立之后就將連接注冊到其他Worker線程中。
- 多個Worker線程,有時候也叫IO線程,就是專門負責IO讀寫的。
一種實現方式就是像Netty一樣,每個Worker線程都有自己的Selector,可以負責多個連接的IO讀寫事件,每個連接歸屬于某個線程。
另一種方式實現方式就是有專門的線程負責IO事件監聽,這些線程有自己的Selector,一旦監聽到有IO讀寫事件,并不是像第一種實現方式那樣(自己去執行IO操作),而是將IO操作封裝成一個Runnable交給Worker線程池來執行,這種情況每個連接可能會被多個線程同時操作,相比第一種并發性提高了,但是也可能引來多線程問題,在處理上要更加謹慎些。tomcat的NIO模型就是第二種。
這就要詳細了解下tomcat的NioEndpoint實現了。先來借鑒看下 斷網故障時Mtop觸發tomcat高并發場景下的BUG排查和修復(已被apache采納) 中的一張圖
這張圖勾畫出了NioEndpoint的大致執行流程圖,worker線程并沒有體現出來,它是作為一個線程池不斷的執行IO讀寫事件即SocketProcessor(一個Runnable),即這里的Poller僅僅監聽Socket的IO事件,然后封裝成一個個的SocketProcessor交給worker線程池來處理。下面我們來詳細的介紹下NioEndpoint中的Acceptor、Poller、SocketProcessor。
它們處理客戶端連接的主要流程如圖所示:
圖中Acceptor及Worker分別是以線程池形式存在,Poller是一個單線程。注意,與BIO的實現一樣,缺省狀態下,在server.xml中沒有配置<Executor>,則以Worker線程池運行,如果配置了<Executor>,則以基于java concurrent 系列的java.util.concurrent.ThreadPoolExecutor線程池運行。
Acceptor
接收socket線程,這里雖然是基于NIO的connector,但是在接收socket方面還是傳統的serverSocket.accept()方式,獲得SocketChannel對象,然后封裝在一個tomcat的實現類org.apache.tomcat.util.net.NioChannel對象中。然后將NioChannel對象封裝在一個PollerEvent對象中,并將PollerEvent對象壓入events queue里。這里是個典型的生產者-消費者模式,Acceptor與Poller線程之間通過queue通信,Acceptor是events queue的生產者,Poller是events queue的消費者。Poller
Poller線程中維護了一個Selector對象,NIO就是基于Selector來完成邏輯的。在connector中并不止一個Selector,在socket的讀寫數據時,為了控制timeout也有一個Selector,在后面的BlockSelector中介紹。可以先把Poller線程中維護的這個Selector標為主Selector。 Poller是NIO實現的主要線程。首先作為events queue的消費者,從queue中取出PollerEvent對象,然后將此對象中的channel以OP_READ事件注冊到主Selector中,然后主Selector執行select操作,遍歷出可以讀數據的socket,并從Worker線程池中拿到可用的Worker線程,然后將socket傳遞給Worker。整個過程是典型的NIO實現。Worker
Worker線程拿到Poller傳過來的socket后,將socket封裝在SocketProcessor對象中。然后從Http11ConnectionHandler中取出Http11NioProcessor對象,從Http11NioProcessor中調用CoyoteAdapter的邏輯,跟BIO實現一樣。在Worker線程中,會完成從socket中讀取http request,解析成HttpServletRequest對象,分派到相應的servlet并完成邏輯,然后將response通過socket發回client。在從socket中讀數據和往socket中寫數據的過程,并沒有像典型的非阻塞的NIO的那樣,注冊OP_READ或OP_WRITE事件到主Selector,而是直接通過socket完成讀寫,這時是阻塞完成的,但是在timeout控制上,使用了NIO的Selector機制,但是這個Selector并不是Poller線程維護的主Selector,而是BlockPoller線程中維護的Selector,稱之為輔Selector。
并發參數控制
tomcat8.5的配置參數
acceptCount
文檔描述為:
The maximum queue length for incoming connection requests when all possible request processing threads are in use. Any requests received when the queue is full will be refused. The default value is 100.
這個參數就立馬牽涉出一塊大內容:TCP三次握手的詳細過程,這個之后再詳細探討。這里可以簡單理解為:連接在被ServerSocketChannel accept之前就暫存在這個隊列中,acceptCount就是這個隊列的最大長度。ServerSocketChannel accept就是從這個隊列中不斷取出已經建立連接的的請求。所以當ServerSocketChannel accept取出不及時就有可能造成該隊列積壓,一旦滿了連接就被拒絕了acceptorThreadCount
文檔如下描述
The number of threads to be used to accept connections. Increase this value on a multi CPU machine, although you would never really need more than 2. Also, with a lot of non keep alive connections, you might want to increase this value as well. Default value is 1.
Acceptor線程只負責從上述隊列中取出已經建立連接的請求。在啟動的時候使用一個ServerSocketChannel監聽一個連接端口如8080,可以有多個Acceptor線程并發不斷調用上述ServerSocketChannel的accept方法來獲取新的連接。參數acceptorThreadCount其實使用的Acceptor線程的個數。maxConnections
文檔描述如下
The maximum number of connections that the server will accept and process at any given time. When this number has been reached, the server will accept, but not process, one further connection. This additional connection be blocked until the number of connections being processed falls below maxConnections at which point the server will start accepting and processing new connections again. Note that once the limit has been reached, the operating system may still accept connections based on the acceptCount setting. The default value varies by connector type. For NIO and NIO2 the default is 10000. For APR/native, the default is 8192.
Note that for APR/native on Windows, the configured value will be reduced to the highest multiple of 1024 that is less than or equal to maxConnections. This is done for performance reasons. If set to a value of -1, the maxConnections feature is disabled and connections are not counted.
這里就是tomcat對于連接數的一個控制,即最大連接數限制。一旦發現當前連接數已經超過了一定的數量(NIO默認是10000),上述的Acceptor線程就被阻塞了,即不再執行ServerSocketChannel的accept方法從隊列中獲取已經建立的連接。但是它并不阻止新的連接的建立,新的連接的建立過程不是Acceptor控制的,Acceptor僅僅是從隊列中獲取新建立的連接。所以當連接數已經超過maxConnections后,仍然是可以建立新的連接的,存放在上述acceptCount大小的隊列中,這個隊列里面的連接沒有被Acceptor獲取,就處于連接建立了但是不被處理的狀態。當連接數低于maxConnections之后,Acceptor線程就不再阻塞,繼續調用ServerSocketChannel的accept方法從acceptCount大小的隊列中繼續獲取新的連接,之后就開始處理這些新的連接的IO事件了。maxThreads
文檔描述如下
The maximum number of request processing threads to be created by this Connector, which therefore determines the maximum number of simultaneous requests that can be handled. If not specified, this attribute is set to 200. If an executor is associated with this connector, this attribute is ignored as the connector will execute tasks using the executor rather than an internal thread pool.
這個簡單理解就算是上述worker的線程數。他們專門用于處理IO事件,默認是200。
Ref:
http://www.cnblogs.com/hggen/p/6264475.html
http://blog.csdn.net/qq_16681169/article/details/75003640