如何優雅關閉 Spring Boot 應用

前言

隨着線上應用逐步採用 SpringBoot 構建,SpringBoot應用實例越來多,當線上某個應用須要升級部署時,經常簡單粗暴地使用 kill 命令,這種中止應用的方式會讓應用將全部處理中的請求丟棄,響應失敗。這樣的響應失敗尤爲是在處理重要業務邏輯時須要極力避免的,那麼有什麼更好的方式來平滑地關閉 SpringBoot 應用呢?那就經過本文一塊兒來探究吧。(本文主要針對基於Spring Boot 內嵌 Tomcat 容器做爲 Web 服務的應用)html

本文示例代碼能夠經過下面倉庫地址獲取:git

環境支持:github

  • JDK 8
  • SpringBoot 2.1.4
  • Maven 3.6.0

定製 Tomcat Connector 行爲

要平滑關閉 Spring Boot 應用的前提就是首先要關閉其內置的 Web 容器,再也不處理外部新進入的請求。爲了能讓應用接受關閉事件通知的時候,保證當前 Tomcat 處理全部已經進入的請求,咱們須要實現 TomcatConnectorCustomizer 接口,這個接口的源碼十分簡單,從註釋能夠看出這是實現自定義 Tomcat Connector 行爲的回調接口:web

這裏若是小夥伴對 Connector 不太熟悉,我就簡單描述下: Connector 屬於 Tomcat 抽象組件,功能就是用來接受外部請求,以及內部傳遞,並返回響應內容,是Tomcat 中請求處理和響應的重要組件,具體實現有 HTTP Connector 和 AJP Connector。spring

經過定製 Connector 的行爲,咱們就能夠容許在請求處理完畢後進行 Tomcat 線程池的關閉,具體實現代碼以下:shell

上述代碼定義的 TIMEOUT 變量爲 Tomcat 線程池延時關閉的最大等待時間,一旦超過這個時間就會強制關閉線程池,也就沒法處理全部請求了,咱們經過控制 Tomcat 線程池的關閉時機,來實現優雅關閉 Web 應用的功能。另外須要注意的是咱們的類 CustomShutdown 實現了 ApplicationListener 接口,意味着監聽着 Spring 容器關閉的事件,即當前的 ApplicationContext 執行 close 方法。apache

##內嵌 Tomcat 添加 Connector 回調tomcat

有了定製的 Connector 回調,咱們須要在啓動過程當中添加到內嵌的 Tomcat 容器中,而後等待執行。那這一步又是如何實現的呢,能夠參考下面代碼:springboot

這裏的 TomcatServletWebServerFactory 是 Spring Boot 實現內嵌 Tomcat 的工廠類,相似的其餘 Web 容器,也有對應的工廠類如 JettyServletWebServerFactory,UndertowServletWebServerFactory。他們共同的特色就是繼承同個抽象類 AbstractServletWebServerFactory,提供了 Web 容器默認的公共實現,如應用上下文設置,會話管理等。app

若是咱們須要定義Spring Boot 內嵌的 Tomcat 容器時,就可使用 TomcatServletWebServerFactory 來進行個性化定義,例以下方爲官方文檔提供自定示例:

好了說回正題,咱們這裏使用 addConnectorCustomizers 方法將自定義的 Connector 行爲添加到內嵌的Tomcat 之上,爲了查看加載效果,咱們能夠在 Spring Boot 程序啓動後從容器中獲取下webServerFactory 對象,而後觀察,在它的 tomcatConnectorCustomizers 屬性中能夠看到已經有了 CustomeShutdown 對象。

開啓 Shutdown Endpoint

到目前讓內嵌 Tomcat 容器平穩關閉的操做已經完成,接下來要作的就是如何關閉主動關閉 Spring 容器了,除了常規Linux 命令 Kill,咱們能夠利用 Spring Boot Actuator 來實現Spring 容器的遠程關閉,怎麼實現繼續看

Spring Boot Actuator 是 Spring Boot 的一大特性,它提供了豐富的功能來幫助咱們監控和管理生產環境中運行的 Spring Boot 應用。咱們能夠經過 HTTP 或者 JMX 方式來對咱們應用進行管理,除此以外,它爲咱們的應用提供了審計,健康狀態和度量信息收集的功能,能幫助咱們更全面地瞭解運行中的應用。

Actuator, ['æktʃʊˌeɪtə] 中文翻譯過來就是制動器,這是一個製造業的術語,指的是用於控制某物的機械裝置。

在 Spring Boot Actuator 中也提供控制應用關閉的功能,因此咱們要爲應用引入 Spring Boot Actuator,具體方式就是要將對應的 starter 依賴添加到當前項目中,以 Maven 項目爲例:

Spring Boot Actuator 採用向外部暴露 Endpoint (端點)的方式來讓咱們與應用進行監控和管理,引入 spring-boot-starter-actuator 以後,咱們就須要啓用咱們須要的 Shutdown Endpoint,在配置文件 application.properties 中,設置以下

第一行表示啓用 Shutdown Endpoint ,第二行表示向外部以 HTTP 方式暴露全部 Endpoint,默認狀況下除了 Shutdown Endpoint 以外,其餘 Endpoint 都是啓用的。

除了 Shutdown Endpoint,Actuator Endpoint 還有十餘種,有的是特定操做,好比 heapdump 轉儲內存日誌;有的是信息展現,好比 health 顯示應用健康狀態。具體全部 Endpoint 信息能夠參見官方文檔-53. Endpoints 一節。

到這裏咱們的前期配置工做就算完成了。當啓動應用後,就能夠經過POST 方式請求對應路徑的 http://host:port/actuator/shutdown 來實現Spring Boot 應用遠程關閉,是否是很簡單呢。

模擬測試

這裏爲了模擬測試,咱們首先模擬實現長達10s 時間處理業務的請求控制器 BusinessController,具體實現以下:

Thread.sleep 來阻塞當前請求線程,模擬業務處理,在此同時用 HTTP 方式訪問 Shutdown Endpoint 試圖關閉應用,能夠經過觀察控制檯日誌看是否應用是否會完成請求的處理後才真正進行關閉。

首先用 curl 命令模擬發送業務請求:

而後在業務處理中,直接發送請求 actuator/shutdown,嘗試關閉應用,一樣採用 curl 方式:

actuator/shutdown 請求發送後會當即返回響應結果,但應用並不會中止:

最後看下控制檯的日誌輸出順序:

能夠看出在發送業務請求以後馬上發送關閉應用的請求,並不會當即將應用中止,而是在請求處理完畢以後,就是阻塞的 10s 後應用開始退出,這樣能夠保證已經接收到的請求能返回正常響應, 而關閉請求以後再進入的請求都不會被處理,到這裏咱們優雅關閉 Spring Boot 程序的操做就此實現了。

實現自動化

因爲 Spring Boot 提供內嵌 Web 容器的便利性,咱們常常將程序打包成 jar 而後發佈。一般應用的啓動和關閉操做流程是固定且重複的,本着 Don't Repeat Yourself 原則,咱們有必要將這個操做過程自動化,將關閉和啓用的 SpringBoot應用的操做寫成 shell 腳本,以免出現人爲的差錯,而且方便使用,提升操做效率。下面是我針對示例程序所寫的程序啓動腳本:(具體腳本可在示例項目查看)

有了腳本,咱們能夠直接經過命令行方式平滑地更新部署 Spring Boot 程序,效果以下:

總結

本文主要探究瞭如何對基於Spring Boot 內嵌 Tomcat 的 Web 應用進行平滑關閉的實現,若是採用其餘 Web 容器也相似方式,但願這邊文章有所幫助,如有錯誤或者不當之處,還請你們批評指正,一塊兒學習交流。

參考

相關文章
相關標籤/搜索