Tomcat容器的Server模塊有管理容器的啟動和關閉、管理了容器內的服務組件Service、管理了全局JNDI資源的功能,對Tomcat容器的生命周期管理有重要意義。Tomcat的服務組件則是Tomcat的兩個核心組件連接器和servlet容器之間的橋梁。本文會對Tomcat容器的服務器組件Server和服務組件Service進行介紹。
服務器組件Server
我們知道Tomcat容器啟動之后就可以一直保持服務,即使請求出現異常也不會退出,只有在收到特定的容器關閉命令時才會退出。Tomcat容器是怎么實現容器的啟動?啟動之后是如何保證容器一直保持運行?在收到容器關閉命令的時候怎么優雅關閉的呢?這就是Tomcat容器中的Server的功能了。
每個Tomcat容器都會唯一包含一個Server組件,對應於Tomcat安裝文件夾下面的server.xml。下面為Tomcat10安裝包中conf/server.xml的默認配置。分析xml可知,server節點有 port和shutdown屬性,包含Listener、GlobalNamintResources和Service三部分子節點。下文我們會分別對這些內容進行介紹。
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
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 "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>
Server的啟動/停止
通過上文可以知道,Server組件重要的就是控制Tomcat容器的啟動/停止,然而啟動停止並不是簡單的啟動JVM關閉JVM就可以了,Tomcat容器啟動/停止是還必須調用容器內所有組件的生命周期方法,啟動時需要所有的組件進行初始化,結束時需要所有的組件進行銷毀和資源釋放。
JVM的啟動/停止
JVM的啟動比較簡單,我們在運行tomcat啟動腳本的時候,會啟動tomcat的Jar文件,從而啟動JVM。
關於JVM的退出則稍微復雜一些,JVM退出的方式分為以下三種類型:
- 正常關閉:當最后一個非守護線程結束或者調用了System.exit或者通過其他特定平台的方法關閉(發送SIGINT,SIGTERM信號等)
- 異常關閉:運行中遇到RuntimeException異常等。
- 強制關閉:通過調用Runtime.halt方法或者是在操作系統中直接kill(發送SIGKILL信號)掉JVM進程
對於正常關閉和異常關閉,JVM都有機會執行關閉的Hook方法,對於強制關閉則不一定會執行關閉時的hook方法。所以我們在日常使用中應該盡量避免使用kill -9等方法退出JVM。
JVM注冊Shutdown Hook的方法如下所示:
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
//
}
});
Tomcat容器啟動的時候會通過Runtime.getRuntime().addShutdownHook(Runnable run)
方法向JVM注冊關閉回調方法CatalinaShutdownHook,從而實現容器的優雅關閉。
Tomcat關閉接口
我們上面講了JVM退出的情況下Tomcat怎么實現優雅的關閉,Tomcat也可以主動關閉程序,我們在配置server.xml文件的時候,會指定server的port和shutdown指令,在需要關閉Tomcat容器的時候,我們只需要向指定端口發送關閉指令,Tomcat就會主動退出服務。
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
</Server>
生命周期的控制
Tomcat中需要實現聲明周期管理的組件都會實現Lifecycle接口。通過上文我們知道Tomcat的啟動/停止是由Server控制的,那么Server是如何通知容器內的其它組件(如container、connector)啟動/停止相關事件的呢?我們先看看Tomcat的結構圖,我們可以看到Tomcat容器的組件之間是一層層包含關系,一個Server包含多個Service,一個Service包含多個Container等等。
Tomcat容器在關閉的時候會通知所有的子組件(service組件)容器關閉事件,service組件再通知它的所有子組件容器關閉事件。事件通過父子關系層層傳遞到各個組件,從而實現組件之間的生命周期管理。
事實上Tomcat容器的生命周期事件不僅僅包含啟動/關閉,而是更詳細的划分了啟動關閉的各個階段,分為以下代碼示例中的各個事件。
public static final String BEFORE_INIT_EVENT = "before_init";
public static final String AFTER_INIT_EVENT = "after_init";
public static final String START_EVENT = "start";
public static final String BEFORE_START_EVENT = "before_start";
public static final String AFTER_START_EVENT = "after_start";
public static final String STOP_EVENT = "stop";
public static final String BEFORE_STOP_EVENT = "before_stop";
public static final String AFTER_STOP_EVENT = "after_stop";
public static final String AFTER_DESTROY_EVENT = "after_destroy";
public static final String BEFORE_DESTROY_EVENT = "before_destroy";
public static final String PERIODIC_EVENT = "periodic";
public static final String CONFIGURE_START_EVENT = "configure_start";
public static final String CONFIGURE_STOP_EVENT = "configure_stop";
Service服務組件
Server中Service的配置如下所示,Service組件包含兩種組件:連接器和Servlet容器,其中servlet容器只有一個,連接器可以由多個。多個連接器可以使Tomcat為多種不同的請求協議提供服務,比如一個處理HTTP請求,另外一個處理HTTPS請求。
連接器負責將Socket請求解析為Request和Response,而Servlet容器則負責根據業務邏輯處理請求中的Request和Response,Service服務組件則負責把二者關聯起來。我會在其它文章中詳細介紹Servlet容器和連接器Connector。
每個連接器組件Connector都可以指定一個Servlet容器處理其解析得到的Request和Response,所以Service的功能比較簡單,就是為Service中的每個組件設置Servlet容器。
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
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 "%r" %s %b" />
</Host>
</Engine>
</Service>
Server的其它配置
全局資源GlobalNamingResources
提供了容器級別的JNDI資源配置。比如下面的默認配置,就提供了Tomcat用戶數據的JNDI,存儲在conf/tomcat-users.xml中。容器資源對容器的依賴性比較高,現在的使用場景比較少。
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
監聽器Listener
監聽器用來監聽容器的特定事件,如容器的啟動關閉事件等。如下所示,默認的server.xml中包含了5個監聽器,我們接下來會簡單介紹默認監聽器的功能。
<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
</Server>
- VersionLoggerListener:在容器啟動前打印各種版本信息,如JVM版本、操作系統版本、tomcat版本等信息。
- AprLifecycleListener:APR的生命周期處理,APR(Apache portable Run-time libraries,Apache可移植運行庫)的目的如其名稱一樣,主要為上層的應用程序提供一個可以跨越多操作系統平台使用的底層支持接口庫。
- JreMemoryLeakPreventionListener:用於處理上下文類加載器可能出現的內存泄露問題,啟動Java內存自動回收任務,每小時觸發FullGC。
- GlobalResourcesLifecycleListener:Tomcat啟動時實例化JNDI資源的MBean,Tomcat停止時銷毀MBean.
- ThreadLocalLeakPreventionListener:在Context關閉的時候清空線程上下文,防止ThreadLocal內存泄露。
我是御狐神,歡迎大家關注我的微信公眾號:wzm2zsd
本文最先發布至微信公眾號,版權所有,禁止轉載!