如何優雅關閉 Spring Boot 應用 如何優雅關閉 Spring Boot 應用前言定製 Tomcat Connector 行為內嵌 Tomcat 添加 Connector 回調開啟 Shutdown Endpoint模擬測試實現自動化總結參考 如何優雅關閉 Spring Boot 應用前言定 ...
如何優雅關閉 Spring Boot 應用前言定製 Tomcat Connector 行為內嵌 Tomcat 添加 Connector 回調開啟 Shutdown Endpoint模擬測試實現自動化總結參考
前言
隨著線上應用逐步採用 SpringBoot 構建,SpringBoot應用實例越來多,當線上某個應用需要升級部署時,常常簡單粗暴地使用 kill 命令,這種停止應用的方式會讓應用將所有處理中的請求丟棄,響應失敗。這樣的響應失敗尤其是在處理重要業務邏輯時需要極力避免的,那麼有什麼更好的方式來平滑地關閉 SpringBoot 應用呢?那就通過本文一起來探究吧。(本文主要針對基於Spring Boot 內嵌 Tomcat 容器作為 Web 服務的應用)
本文示例代碼可以通過下麵倉庫地址獲取:
springboot-shutdown:https://github.com/wrcj12138aaa/springboot-shutdown
環境支持:
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 容器也類似方式,希望這邊文章有所幫助,若有錯誤或者不當之處,還請大家批評指正,一起學習交流。
參考
-
Graceful Shutdown Spring Boot Applications:https://blog.marcosbarbero.com/graceful-shutdown-spring-boot-apps/
-
Shutdown a Spring Boot Application:https://www.baeldung.com/spring-boot-shutdown
-
官方文檔-53. Endpoints:https://docs.spring.io/spring-boot/docs/2.1.4.RELEASE/reference/htmlsingle/#production-ready-endpoints
-
The HTTP Connector:https://tomcat.apache.org/tomcat-8.5-doc/config/http.html
-
Customizing ConfigurableServletWebServerFactory Directly: