如何優雅關閉 Spring Boot 應用


如何優雅關閉 Spring Boot 應用

前言

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

本文示例代碼可以通過下面倉庫地址獲取:

環境支持:

  • JDK 8

  • SpringBoot 2.1.4

  • Maven 3.6.0

 

定制 Tomcat Connector 行為

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

 

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

通過定制 Connector 的行為,我們就可以允許在請求處理完畢后進行 Tomcat 線程池的關閉,具體實現代碼如下:

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

內嵌 Tomcat 添加 Connector 回調

有了定制的 Connector 回調,我們需要在啟動過程中添加到內嵌的 Tomcat 容器中,然后等待執行。那這一步又是如何實現的呢,可以參考下面代碼:

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

如果我們需要定義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 容器也類似方式,希望這邊文章有所幫助,若有錯誤或者不當之處,還請大家批評指正,一起學習交流。

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM