從原理到最佳實踐詳解SpringBoot如何實現(xiàn)優(yōu)雅停機
微服務(wù)和云原生時代,應(yīng)用的平滑啟停是保障系統(tǒng)高可用的關(guān)鍵環(huán)節(jié)。Spring Boot 在 2.3 版本引入了對內(nèi)嵌 Web 容器優(yōu)雅停機的原生支持,改變此前需要手動編碼實現(xiàn)局面。
一、什么是“優(yōu)雅停機”
優(yōu)雅停機(Graceful Shutdown) 是指在應(yīng)用停止過程中:
- 立即停止接收新請求;
- 允許已進入的請求繼續(xù)處理完成;
- 安全釋放資源(數(shù)據(jù)庫連接、線程池、緩存等);
- 避免客戶端收到 5xx 錯誤或連接中斷。
若未實現(xiàn)優(yōu)雅停機,在滾動更新、縮容或重啟時,極易出現(xiàn)如下典型異常:
ERROR ... - Error creating bean with name 'orderController':
Singleton bean creation not allowed while singletons of this factory are in destruction
org.springframework.beans.factory.BeanCreationNotAllowedException: ...
異常表明:Spring 容器正在銷毀,但仍有請求試圖獲取 Controller Bean —— 這正是停機流程設(shè)計不當(dāng)?shù)闹苯芋w現(xiàn)。
二、核心組件與層級關(guān)系
要理解停機流程,需先厘清以下組件的包含關(guān)系:
JVM
└── 內(nèi)嵌 Web 容器(如 Tomcat)
└── Servlet 容器(StandardContext)
└── DispatcherServlet(Spring MVC 入口)
└── Spring ApplicationContext(IoC 容器)
└── 所有 Spring Bean(@Controller, @Service 等)
- Web 容器(Tomcat/Jetty/Undertow):負責(zé)網(wǎng)絡(luò)連接與線程調(diào)度。
- Servlet 容器:管理 Servlet、Filter 生命周期。
- Spring 容器:即
ApplicationContext,管理 Bean 的創(chuàng)建與銷毀。 - Spring Boot:不是容器,而是自動集成 Web 容器 + Spring 容器的啟動框架。
關(guān)鍵點:Spring 容器運行在 Web 容器內(nèi)部。停機時,必須協(xié)調(diào)二者關(guān)閉順序,否則就會出現(xiàn)“Web 容器還在處理請求,但 Spring 容器已拒絕服務(wù)”的競態(tài)條件。
三、Spring Boot < 2.3:手動實現(xiàn)的“偽優(yōu)雅”
在2.3之前,Spring Boot 沒有內(nèi)置優(yōu)雅停機能力。默認行為是:
- 收到
SIGTERM(如kill -15); - 立即關(guān)閉 Spring 容器 → 設(shè)置
singletonsCurrentlyInDestruction = true; - Web 容器仍在運行,繼續(xù) accept 新連接并分配線程;
- 若此時有請求進入,
DispatcherServlet嘗試通過getBean("orderController")獲取 Controller; - 因 Spring 容器已標(biāo)記為“銷毀中”,拋出
BeanCreationNotAllowedException。
問題本質(zhì):關(guān)閉順序錯誤
先關(guān) Spring,再關(guān) Web 容器 → 存在危險時間窗口。
開發(fā)者workaround
需手動實現(xiàn)以下邏輯:
@Component
public class GracefulShutdown implements TomcatConnectorCustomizer,
ApplicationListener<ContextClosedEvent> {
private volatile Connector connector;
@Override
public void customize(Connector connector) {
this.connector = connector;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
connector.pause(); // 停止接收新請求
Executor executor = connector.getProtocolHandler().getExecutor();
if (executor instanceof ThreadPoolExecutor) {
try {
((ThreadPoolExecutor) executor).shutdown();
((ThreadPoolExecutor) executor).awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
同時需注冊到 Tomcat:
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addConnectorCustomizers(gracefulShutdown());
return tomcat;
}
即便如此,仍無法完美處理 HTTP/1.1 keep-alive 連接復(fù)用帶來的新請求。
四、Spring Boot ≥ 2.3:原生優(yōu)雅停機
Spring Boot 2.3 引入了 標(biāo)準(zhǔn)化的優(yōu)雅停機機制,通過兩行配置即可啟用:
server:
shutdown: graceful # 啟用優(yōu)雅停機
spring:
lifecycle:
timeout-per-shutdown-phase: 30s # 等待超時時間
核心思想:反轉(zhuǎn)關(guān)閉順序 + 流量控制前置
停機流程變?yōu)椋?/p>
- Web 容器立即 pause:停止 accept 新連接(Tomcat 調(diào)用
connector.pause()); - 等待已進入的請求完成:最多等待
timeout-per-shutdown-phase時間; - 確認無活躍請求后,關(guān)閉 Spring 容器:銷毀所有 Bean;
- 徹底停止 Web 容器線程池。
底層原理
1.SmartLifecycle控制關(guān)閉階段
Spring Boot 注冊了 WebServerGracefulShutdownLifecycle,它實現(xiàn) SmartLifecycle 接口,并設(shè)置:
@Override
public int getPhase() {
return Integer.MAX_VALUE; // 最后啟動,最先停止
}
確保其 stop() 方法在普通 Bean 銷毀之前執(zhí)行。
2.WebServer 接口標(biāo)準(zhǔn)化
不同內(nèi)嵌服務(wù)器實現(xiàn)統(tǒng)一的 GracefulShutdown 行為:
- Tomcat:
pause()+ 等待線程池 - Jetty:停止 acceptor 線程
- Undertow:返回 503 給新請求
- Reactor Netty:發(fā)送 GOAWAY(HTTP/2)
3.與 Spring 生命周期深度集成
通過 LifecycleProcessor 協(xié)調(diào)所有 SmartLifecycle Bean 的關(guān)閉順序,確保 Web 容器優(yōu)雅關(guān)閉完成之后,才觸發(fā) ApplicationContext.close()。
五、2.3 前后對比總表
| 維度 | Spring Boot < 2.3 | Spring Boot ≥ 2.3 |
|---|---|---|
| 是否內(nèi)置支持 | 需手動編碼 | 開箱即用 |
| 關(guān)閉起點 | Spring 容器 | Web 容器 |
| 新請求處理 | 繼續(xù) accept → 高風(fēng)險 | 立即拒絕(pause/503)→ 安全 |
| 已進入請求 | 可能因 Spring 關(guān)閉而失敗 | 等待完成(最多 timeout) |
| 典型異常 | BeanCreationNotAllowedException 高頻 | 極少(除非超時) |
| 配置復(fù)雜度 | 高(需自定義監(jiān)聽器) | 低(僅需 YAML) |
| 多容器支持 | 僅限手動適配 | 自動支持 Tomcat/Jetty/Undertow/Netty |
| K8s 友好度 | 差 | 優(yōu)秀 |
六、生產(chǎn)環(huán)境最佳實踐
1.啟用優(yōu)雅停機(2.3+)
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 45s # > 業(yè)務(wù)最大請求耗時
2.Kubernetes 部署配合 preStop
spec:
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["sleep", "15"] # 給 LB 時間摘除節(jié)點
terminationGracePeriodSeconds: 60
3.Windows 服務(wù)需走 Actuator
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: "shutdown"
并通過外部命令觸發(fā):
curl -X POST http://localhost:8080/actuator/shutdown
4.監(jiān)控與告警
- 監(jiān)控停機期間的
5xx錯誤率; - 若仍有
BeanCreationNotAllowedException,檢查:- 超時時間是否足夠;
- LB 摘除延遲;
- 是否存在長輪詢/WebSocket 未處理。
到此這篇關(guān)于從原理到最佳實踐詳解SpringBoot如何實現(xiàn)優(yōu)雅停機的文章就介紹到這了,更多相關(guān)SpringBoot停機內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過實例深入學(xué)習(xí)Java的Struts框架中的OGNL表達式使用
這篇文章主要通過實例介紹了Java的Strus框架中的OGNL表達式使用,Struts框架是Java的SSH三大web開發(fā)框架之一,需要的朋友可以參考下2015-11-11
Java實現(xiàn)自動化合并與拆分PowerPoint表格單元格
通過 Spire.Presentation for Java,我們可以利用代碼精準(zhǔn)地控制表格的每一個角落,本文將詳細介紹如何在 Java 后端實現(xiàn) PPT 表格的合并與拆分操作,需要的可以了解下2026-02-02
Java?Stream對象并行處理方法parallel()代碼示例
在Java中Stream是一種用于處理集合數(shù)據(jù)的流式操作API,它提供了一種簡潔、靈活、高效的方式來對集合進行各種操作,下面這篇文章主要給大家介紹了關(guān)于Java?Stream對象并行處理方法parallel()的相關(guān)資料,需要的朋友可以參考下2023-11-11
Java實現(xiàn)mysql數(shù)據(jù)庫的自動備份和自動還原
這篇文章主要為大家詳細介紹了如何通過Java實現(xiàn)mysql數(shù)據(jù)庫的自動備份和自動還原,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下2024-11-11

