Tomcat 優雅關閉之路
本文首發於 vivo網際網路技術 微信公眾號
連結: https://mp.weixin.qq.com/s/ZqkmoAR4JEYr0x0Suoq7QQ
作者:馬運傑
本文透過閱讀Tomcat啟動和關閉流程的原始碼,深入分析不同的Tomcat關閉方式背後的原理,讓開發人員能夠了解在使用不同的關閉方式時需要注意的點,避免因JVM程式異常退出導致的各種非預見性錯誤。
一、 Tomcat的啟動過程
要了解Tomcat關閉的原理,首先需要關注下Tomcat是如何啟動的。這裡我們簡單介紹下。
Tomcat啟動的入口是Bootstrap類中的main方法,而後根據server.xml中的配置,對Server、Service、Enigin、Connector、Host、Context等元件進行初始化,之後便是啟動這些元件。我們重點來看下啟動之後,Tomcat做了哪些工作。
在Tomcat的各元件啟動完畢之後,main主執行緒會進入Catalina.out的await()方法,而此方法又是主要呼叫了Server元件的await()方法,從名字便可以看出,這個方法的目的是為了阻塞當前執行緒(main主執行緒)。
分析await的原始碼(原始碼比較長,這裡擷取了部分,全部的可以自行拉取Tomcat原始碼進行閱讀)。
(StandardServer.await())
我們發現await()方法主要是根據server.xml中Server節點port屬性的設定做了以下幾種工作:
-
port為-2時,函式直接退出,此時主執行緒不會阻塞。
-
port為-1時,將等待執行緒設定為當前執行緒,並且進入while迴圈,直到stopAwait標誌位置為true
-
port為其他時,則會新建一個socket服務端,該socket繫結了當前伺服器的ip以及port埠,隨後設定等待執行緒為當前執行緒,並且socket進入阻塞監聽狀態,直到socket監聽到server.xml中預置的關閉字串(預設是"SHUTDOWN")
在主執行緒退出等待後,就會進入Tomcat的關閉流程,進行各個元件的stop和destroy操作。從上述分析可以看出,要想停止Tomcat,就是要中斷main主執行緒的等待狀態。
下圖為Tomcat的整個生命週期。
(Tomcat生命週期)
二、常見的關閉Tomcat的方式
1、我們下載的Tomcat壓縮包的bin目錄下,有一個由官方提供的指令碼(shutdown.sh),可以用來結束Tomcat程式。
2、伺服器上,我們還可以利用kill -x命令來結束Tomcat程式。
3、此外,程式碼中的System.exit()以及OOM等異常情況的發生,也會導致Tomcat程式的關閉,但是這兩者都不是正常的運維手段,在此我們不做分析。
三、shutdown指令碼
1、shutdown.sh的原理
檢視分析官方的shutdown.sh指令碼以及catalina.sh指令碼,發現這兩個指令碼最終是在呼叫Bootstrap類的main方法,和啟動Tomcat時呼叫的是同一個方法,差異在於傳入了"stop"作為main方法的引數,而傳入了該引數的main方法,會呼叫Catalina類的stopServer()方法。在此我們抹去不需要關注的程式碼,可以把整個stopServer()方法簡化為如下4步:
其主要做了兩件事:
-
初始化Server元件,和Tomcat啟動時類似,這一步主要是解析server.xml檔案,然後根據server.xml中的屬性初始化Tomcat元件的成員變數,這裡主要關注Server元件的幾個成員變數:port、address、shutdown,預設值分別為8005、127.0.0.1、SHUTDOWN等,需要和啟動時讀取的server.xml保持一致。
-
往address port所監聽的Socket埠發生“SHUTDOWN”字串。
至此,顯而易見的,這對應了第一小節中的第三種阻塞情況,"SHUTDOWN"字串讓main主執行緒結束了等待狀態,並在接下來透過呼叫各元件的stop()和destroy()方法進行資源的釋放。
2、shutdown指令碼的缺點
雖然shutdown指令碼是由Tomcat官方出品,但是其在實際應用中並不廣泛,主要是由於下面兩個缺點:
-
從上述原理就可以分析出,shutdown指令碼是基於啟動時監聽了相應的埠,這就允許任意人員,只要能夠傳送"SHUTDOWN"字串到相應的埠,就可以對Tomcat程式進行關閉,這對於生產環境是相當危險的。所以一般生產環境會將Server的port屬性設定為-1
-
shutdown指令碼只是結束了main主執行緒的等待狀態,讓其正常的走下去。我們知道,JVM中的執行緒分為守護執行緒和使用者執行緒兩種型別,守護執行緒會在所有使用者執行緒結束後,自動回收,進而導致JVM程式的退出。main主執行緒是一個使用者執行緒,但是隨著程式越來越複雜,可能會出現很多其他的使用者執行緒。比如我們平常開發過程中,常用的建立執行緒池的操作Executors.newFixedThreadPool(n) 便會建立n個使用者執行緒,這些執行緒在main主執行緒退出後,並不會自動回收,從而阻止了JVM的正常退出。所以經常會發生呼叫了shutdown指令碼,但是Tomcat程式無法退出的場景。
四、kill -x
1、kill -9 or kill -15
Linux中的kill -x操作是向目標程式傳送對應的訊號量。可以用kill -l命令檢視每個數值所代表的訊號量的值。
(kill訊號量)
這裡面,我們經常會使用kill-9這一命令,kill -9會立即強制結束當前程式,這個操作既方便,但同時也極具破壞性。在實際的環境中,我們可能有在running的任務,如果此時程式被強制關閉,便會導致當前任務資料的丟失,特別是時間特別長的任務,極有可能造成前功盡棄的局面。同時,如果程式設計不當,沒有相應的冪等操作,還有可能會造成實際環境中資料缺失或者髒資料的產生,對生產環境造成致命的問題。
相比kill -9, kill -15(15只是一個例子,Linux中還有其他的中斷訊號)會相對優雅很多。kill -15是向程式傳送一個TERM的中斷訊號量,在JVM接收到該訊號量後,會響應中斷,進而結束當前程式。而這一操作能夠優雅關閉Tomcat的原因在於,JVM在結束當前程式前,會啟動一系列名為shutdownhook(關閉鉤子)的執行緒,而這些執行緒就會成為我們進行風險控制的工具。接下來我們首先看看Tomcat中的關閉鉤子。
2、shutdownhook關閉鉤子
Tomcat的關閉鉤子的定義是在Catalina類中,有一個名為CatalinaShutdownHook內部類,繼承了Thread類。跟著這個執行緒類中的run()方法往下看,其呼叫了Catalina的stop()方法,而此處stop方法,除了正常去停止各元件外,還會去中斷並快速結束main主執行緒(如果主執行緒還存在的話),最後再呼叫各元件的destroy()方法進行資源釋放。
(Tomcat中的shutdownhook)
除了Tomcat會使用關閉鉤子外,很多中介軟體也會使用到這一非常重要的功能。
我們在平常的開發過程中也可以使用關閉鉤子,可以在程式啟動或者執行階段透過呼叫Runtime.getRuntime().addShutdownHook(shutdownHook)方法進行鉤子的新增,但要注意的是,需要在關閉的流程中加入移除鉤子的程式碼。
Spring中當然也有關閉鉤子的應用,並且還為我們使用關閉鉤子提供了更為友好的程式設計體驗。
在Spring中,關閉鉤子是在AbstractApplicationContext.registerShutdownHook()方法中新增的(下圖中的程式碼),而其關閉鉤子的run方法則會呼叫destroyBeans()方法,其對所有繼承了DisposableBean介面的類呼叫其destroy()方法。
讀到這裡我們就明白了,在平時開發時,如果有使用關閉鉤子的需求,可以透過繼承DisposableBean,並實現其destroy(),很方便的來達到我們回收資源,打掃戰場的目的。
3、shutdownhook的使用注意點
shutdownhook在使用中也並不是可以隨意亂用的,需要注意以下幾點:
-
shutdownhook的呼叫是不保證順序的
-
shutdownhook是JVM結束前呼叫的執行緒,所以該執行緒中的方法應儘量短,並且保證不能發生死鎖的情況,否則也會阻止JVM的正常退出
-
shutdownhook中不能執行System.exit(),否則會導致虛擬機器卡住,而不得不強行殺死程式
五、總結
本文對Tomcat兩種常用關閉方式的原理進行了解讀,從上述分析可以看出,用shutdown.sh指令碼控制Tomcat關閉的方式存在許可權的風險,並且也會由於開發中的執行緒操作導致Tomcat無法關閉,所以這種方法在實際應用中使用情況較少。
而kill -15則能夠安全的殺死Tomcat程式,並且由於JVM shutdownhook的存在,我們可以對整個程式關閉時進行更強有力的控制,退出過程也更為優雅,所以使用較為廣泛。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912579/viewspace-2675115/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 優雅關閉maven-default-http-blocker?MavenHTTPBloC
- 如何優雅得關閉協程呢
- Java優雅關閉執行緒池Java執行緒
- golang程式優雅關閉與重啟Golang
- 如何優雅的關閉Go Channel「譯」Go
- 優雅關閉執行緒池的方案執行緒
- 讀 "優雅關閉的 Go Web 伺服器"GoWeb伺服器
- 【剖析 | SOFARPC 框架】系列之 SOFARPC 優雅關閉剖析RPC框架
- 如何優雅的關閉Java執行緒池Java執行緒
- SpringBoot如何優雅關閉(SpringBoot2.3&Spring Boot2.2)Spring Boot
- 讀 "優雅關閉的 Go Web 伺服器"GoWeb伺服器
- 9. 啟動、關閉tomcatTomcat
- 我們們從頭到尾說一次優雅關閉
- 利用 trap 在 docker 容器優雅關閉前執行環境清理Docker
- Tomcat關閉日誌catalina.outTomcat
- Tomcat 7 伺服器關閉原理Tomcat伺服器
- 關閉tomcat報錯Cannot allocate memoryTomcat
- Spring Boot使用@Async實現非同步呼叫:ThreadPoolTaskScheduler執行緒池的優雅關閉Spring Boot非同步thread執行緒
- LaravelConf 2018《優雅與效能,Laravel 與 swoole 整合之路》筆記Laravel筆記
- 記一次解決tomcat自動關閉的bugTomcat
- 處理weblogic、tomcat關閉不安全的http請求WebTomcatHTTP
- 13. 自動化運維——批量關閉tomcat服務運維Tomcat
- [Web][Tomcat]Tomcat相關WebTomcat
- 面試官:說一說如何優雅的關閉執行緒池,我:shutdownNow,面試官:粗魯!面試執行緒
- Golang優化-優雅退出Golang優化
- 前端通關日記之優雅新增陣列元素前端陣列
- 優雅 VS 效能
- 優雅的PromiseKitPromise
- 【Tomcat】〖問題〗解決“org.apache.catalina.startup.Catalina.stopServer未配置關閉埠。通過OS訊號關閉伺服器。伺服器未關閉。”問題TomcatApacheServer伺服器
- 如何優雅的搞垮伺服器,再優雅的救活伺服器
- 如何在 Swift 中優雅的處理閉包導致的迴圈引用Swift
- 程式設計師修神之路—高併發優雅的做限流(有福利)程式設計師
- Tomcat常用優化配置Tomcat優化
- Tomcat部署及優化Tomcat優化
- JS進擊之路:閉包JS
- 效能優化|Tomcat 服務優化優化Tomcat
- 如何優雅使用 vuexVue
- 優雅的使用UITableViewUIView