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 &quot;%r&quot; %s %b" /> </Host>
</Engine>
</Service>
</Server>

Server的啟動/停止

通過上文可以知道,Server元件重要的就是控制Tomcat容器的啟動/停止,然而啟動停止並不是簡單的啟動JVM關閉JVM就可以了,Tomcat容器啟動/停止是還必須呼叫容器內所有元件的生命週期方法,啟動時需要所有的元件進行初始化,結束時需要所有的元件進行銷燬和資源釋放。

JVM的啟動/停止

JVM的啟動比較簡單,我們在執行tomcat啟動指令碼的時候,會啟動tomcat的Jar檔案,從而啟動JVM。

關於JVM的退出則稍微複雜一些,JVM退出的方式分為以下三種類型:

  1. 正常關閉:當最後一個非守護執行緒結束或者呼叫了System.exit或者通過其他特定平臺的方法關閉(傳送SIGINT,SIGTERM訊號等)
  2. 異常關閉:執行中遇到RuntimeException異常等。
  3. 強制關閉:通過呼叫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 &quot;%r&quot; %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>
  1. VersionLoggerListener:在容器啟動前列印各種版本資訊,如JVM版本、作業系統版本、tomcat版本等資訊。
  2. AprLifecycleListener:APR的生命週期處理,APR(Apache portable Run-time libraries,Apache可移植執行庫)的目的如其名稱一樣,主要為上層的應用程式提供一個可以跨越多作業系統平臺使用的底層支援介面庫。
  3. JreMemoryLeakPreventionListener:用於處理上下文類載入器可能出現的記憶體洩露問題,啟動Java記憶體自動回收任務,每小時觸發FullGC。
  4. GlobalResourcesLifecycleListener:Tomcat啟動時例項化JNDI資源的MBean,Tomcat停止時銷燬MBean.
  5. ThreadLocalLeakPreventionListener:在Context關閉的時候清空執行緒上下文,防止ThreadLocal記憶體洩露。

我是御狐神,歡迎大家關注我的微信公眾號:wzm2zsd

本文最先發布至微信公眾號,版權所有,禁止轉載!