如何優雅的關閉 Spring Boot 應用?

01 前言

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

02 定製 Tomcat Connector 行爲

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

1

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

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

1

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

內嵌 Tomcat 添加 Connector 回調

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

1

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

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

1

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

1


03 開啓 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 項目爲例:

1


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

1

第一行表示啓用 Shutdown Endpoint ,第二行表示向外部以 HTTP 方式暴露所有 Endpoint,默認情況下除了 Shutdown Endpoint 之外,其他 Endpoint 都是啓用的。

除了  Shutdown Endpoint,Actuator Endpoint 還有十餘種,有的是特定操作,比如 heapdump 轉儲內存日誌;有的是信息展示,比如 health 顯示應用健康狀態。

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

04 模擬測試

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

1

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

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

1

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

1

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

1

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

1

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

05 實現自動化

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

1

有了腳本,我們可以直接通過命令行方式平滑地更新部署 Spring Boot 程序,效果如下:

1


06 總結

本文主要探究瞭如何對基於Spring Boot 內嵌 Tomcat 的 Web 應用進行平滑關閉的實現,如果採用其他 Web 容器也類似方式,希望這邊文章有所幫助,若有錯誤或者不當之處,還請大家批評指正,一起學習交流。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章