SpringBoot實現(xiàn)優(yōu)雅停機的三種方式
引言
應(yīng)用的啟停是一個常見操作。然而,突然終止一個正在運行的應(yīng)用可能導致正在處理的請求失敗、數(shù)據(jù)不一致等問題。優(yōu)雅停機(Graceful Shutdown)是指應(yīng)用在接收到停止信號后,能夠妥善處理現(xiàn)有請求、釋放資源,然后再退出的過程。本文將詳細介紹SpringBoot中實現(xiàn)優(yōu)雅停機的三種方式。
什么是優(yōu)雅停機?
優(yōu)雅停機指的是應(yīng)用程序在收到停止信號后,不是立即終止,而是遵循以下步驟有序退出:
- 停止接收新的請求
- 等待正在處理的請求完成
- 關(guān)閉各種資源連接(數(shù)據(jù)庫連接池、線程池、消息隊列連接等)
- 完成必要的清理工作
- 最后退出應(yīng)用
優(yōu)雅停機的核心價值在于:
- 提高用戶體驗,避免請求突然中斷
- 保障數(shù)據(jù)一致性,防止數(shù)據(jù)丟失
方式一:SpringBoot內(nèi)置的優(yōu)雅停機支持
原理與支持版本
從Spring Boot 2.3版本開始,框架原生支持優(yōu)雅停機機制。這是最簡單且官方推薦的實現(xiàn)方式。
當應(yīng)用接收到停止信號(如SIGTERM)時,內(nèi)嵌的Web服務(wù)器(如Tomcat、Jetty或Undertow)會執(zhí)行以下步驟:
- 停止接收新的連接請求
- 設(shè)置現(xiàn)有連接的keepalive為false
- 等待所有活躍請求處理完成或超時
- 關(guān)閉應(yīng)用上下文和相關(guān)資源
配置方法
在application.properties或application.yml中添加簡單配置即可啟用:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
這里的timeout-per-shutdown-phase指定了等待活躍請求完成的最大時間,默認為30秒。
實現(xiàn)示例
下面是一個完整的SpringBoot應(yīng)用示例,啟用了優(yōu)雅停機:
@SpringBootApplication
public class GracefulShutdownApplication {
private static final Logger logger = LoggerFactory.getLogger(GracefulShutdownApplication.class);
public static void main(String[] args) {
SpringApplication.run(GracefulShutdownApplication.class, args);
logger.info("Application started");
}
@RestController
@RequestMapping("/api")
static class SampleController {
@GetMapping("/quick")
public String quickRequest() {
return "Quick response";
}
@GetMapping("/slow")
public String slowRequest() throws InterruptedException {
// 模擬長時間處理的請求
logger.info("Start processing slow request");
Thread.sleep(10000); // 10秒
logger.info("Finished processing slow request");
return "Slow response completed";
}
}
@Bean
public ApplicationListener<ContextClosedEvent> contextClosedEventListener() {
return event -> logger.info("Received spring context closed event - shutting down");
}
}
測試驗證
- 啟動應(yīng)用
- 發(fā)起一個長時間運行的請求:
curl http://localhost:8080/api/slow - 在處理過程中,向應(yīng)用發(fā)送SIGTERM信號:
kill -15 <進程ID>,如果是IDEA開發(fā)環(huán)境,可以點擊一次停止服務(wù)按鈕 - 觀察日志輸出:應(yīng)該能看到應(yīng)用等待長請求處理完成后才關(guān)閉
優(yōu)缺點
優(yōu)點:
- 配置簡單,官方原生支持
- 無需額外代碼,維護成本低
- 適用于大多數(shù)Web應(yīng)用場景
- 與Spring生命周期事件完美集成
缺點:
- 僅支持Spring Boot 2.3+版本
- 對于超出HTTP請求的場景(如長時間運行的作業(yè))需要額外處理
- 靈活性相對較低,無法精細控制停機流程
- 只能設(shè)置統(tǒng)一的超時時間
適用場景
- Spring Boot 2.3+版本的Web應(yīng)用
- 請求處理時間可預(yù)期,不會有超長時間運行的請求
- 微服務(wù)架構(gòu)中的標準服務(wù)
方式二:使用Actuator端點實現(xiàn)優(yōu)雅停機
原理與實現(xiàn)
Spring Boot Actuator提供了豐富的運維端點,其中包括shutdown端點,可用于觸發(fā)應(yīng)用的優(yōu)雅停機。這種方式的獨特之處在于它允許通過HTTP請求觸發(fā)停機過程,適合需要遠程操作的場景。
配置步驟
- 添加Actuator依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 啟用并暴露shutdown端點:
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: shutdown
base-path: /management
server:
port: 8081 # 可選:為管理端點設(shè)置單獨端口
使用方法
通過HTTP POST請求觸發(fā)停機:
curl -X POST http://localhost:8081/management/shutdown
請求成功后,會返回類似如下響應(yīng):
{
"message": "Shutting down, bye..."
}
安全性考慮
由于shutdown是一個敏感操作,必須考慮安全性:
spring:
security:
user:
name: admin
password: secure_password
roles: ACTUATOR
management:
endpoints:
web:
exposure:
include: shutdown
endpoint:
shutdown:
enabled: true
# 配置端點安全
management.endpoints.web.base-path: /management
使用安全配置后的訪問方式:
curl -X POST http://admin:secure_password@localhost:8080/management/shutdown
完整實現(xiàn)示例
@SpringBootApplication
@EnableWebSecurity
public class ActuatorShutdownApplication {
private static final Logger logger = LoggerFactory.getLogger(ActuatorShutdownApplication.class);
public static void main(String[] args) {
SpringApplication.run(ActuatorShutdownApplication.class, args);
logger.info("Application started with Actuator shutdown enabled");
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/management/**").hasRole("ACTUATOR")
.anyRequest().permitAll()
.and()
.httpBasic();
return http.build();
}
@RestController
static class ApiController {
@GetMapping("/api/hello")
public String hello() {
return "Hello, world!";
}
}
@Bean
public ApplicationListener<ContextClosedEvent> shutdownListener() {
return event -> {
logger.info("Received shutdown signal via Actuator");
// 等待活躍請求完成
logger.info("Waiting for active requests to complete...");
try {
Thread.sleep(5000); // 簡化示例,實際應(yīng)監(jiān)控活躍請求
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
logger.info("All requests completed, shutting down");
};
}
}
優(yōu)缺點
優(yōu)點:
- 可以通過HTTP請求遠程觸發(fā)停機
- 適合管理工具和運維腳本集成
- 可以與Spring Security集成實現(xiàn)訪問控制
- 支持所有Spring Boot版本(包括2.3之前的版本)
缺點:
- 需要額外配置和依賴
- 潛在的安全風險,需謹慎保護端點
- 對于內(nèi)部復(fù)雜資源的關(guān)閉需要額外編碼
適用場景
- 需要通過HTTP請求觸發(fā)停機的場景
- 使用運維自動化工具管理應(yīng)用的部署
- 集群環(huán)境中需要按特定順序停止服務(wù)
- 內(nèi)部管理系統(tǒng)需要直接控制應(yīng)用生命周期
方式三:自定義ShutdownHook實現(xiàn)優(yōu)雅停機
原理與實現(xiàn)
JVM提供了ShutdownHook機制,允許在JVM關(guān)閉前執(zhí)行自定義邏輯。通過注冊自定義的ShutdownHook,我們可以實現(xiàn)更加精細和靈活的優(yōu)雅停機控制。這種方式的優(yōu)勢在于可以精確控制資源釋放順序,適合有復(fù)雜資源管理需求的應(yīng)用。
基本實現(xiàn)步驟
- 創(chuàng)建自定義的ShutdownHandler類
- 注冊JVM ShutdownHook
- 在Hook中實現(xiàn)自定義的優(yōu)雅停機邏輯
完整實現(xiàn)示例
以下是一個包含詳細注釋的完整示例:
@SpringBootApplication
public class CustomShutdownHookApplication {
private static final Logger logger = LoggerFactory.getLogger(CustomShutdownHookApplication.class);
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(CustomShutdownHookApplication.class, args);
// 注冊自定義ShutdownHook
registerShutdownHook(context);
logger.info("Application started with custom shutdown hook");
}
private static void registerShutdownHook(ConfigurableApplicationContext context) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
logger.info("Executing custom shutdown hook");
try {
// 1. 停止接收新請求(如果是Web應(yīng)用)
if (context.containsBean("tomcatServletWebServerFactory")) {
TomcatServletWebServerFactory server = context.getBean(TomcatServletWebServerFactory.class);
logger.info("Stopping web server to reject new requests");
// 注意: 實際應(yīng)用中需要找到正確方式停止特定Web服務(wù)器
}
// 2. 等待活躍請求處理完成
logger.info("Waiting for active requests to complete");
// 這里可以添加自定義等待邏輯,如檢查活躍連接數(shù)或線程狀態(tài)
Thread.sleep(5000); // 簡化示例
// 3. 關(guān)閉自定義線程池
shutdownThreadPools(context);
// 4. 關(guān)閉消息隊列連接
closeMessageQueueConnections(context);
// 5. 關(guān)閉數(shù)據(jù)庫連接池
closeDataSourceConnections(context);
// 6. 執(zhí)行其他自定義清理邏輯
performCustomCleanup(context);
// 7. 最后關(guān)閉Spring上下文
logger.info("Closing Spring application context");
context.close();
logger.info("Graceful shutdown completed");
} catch (Exception e) {
logger.error("Error during graceful shutdown", e);
}
}, "GracefulShutdownHook"));
}
private static void shutdownThreadPools(ApplicationContext context) {
logger.info("Shutting down thread pools");
// 獲取所有ExecutorService類型的Bean
Map<String, ExecutorService> executors = context.getBeansOfType(ExecutorService.class);
executors.forEach((name, executor) -> {
logger.info("Shutting down executor: {}", name);
executor.shutdown();
try {
// 等待任務(wù)完成
if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
logger.warn("Executor did not terminate in time, forcing shutdown: {}", name);
executor.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.warn("Interrupted while waiting for executor shutdown: {}", name);
executor.shutdownNow();
}
});
}
private static void closeMessageQueueConnections(ApplicationContext context) {
logger.info("Closing message queue connections");
// 示例:關(guān)閉RabbitMQ連接
if (context.containsBean("rabbitConnectionFactory")) {
try {
ConnectionFactory rabbitFactory = context.getBean(ConnectionFactory.class);
// 適當?shù)仃P(guān)閉連接
logger.info("Closed RabbitMQ connections");
} catch (Exception e) {
logger.error("Error closing RabbitMQ connections", e);
}
}
// 示例:關(guān)閉Kafka連接
if (context.containsBean("kafkaConsumerFactory")) {
try {
// 關(guān)閉Kafka連接的代碼
logger.info("Closed Kafka connections");
} catch (Exception e) {
logger.error("Error closing Kafka connections", e);
}
}
}
private static void closeDataSourceConnections(ApplicationContext context) {
logger.info("Closing datasource connections");
// 獲取所有DataSource類型的Bean
Map<String, DataSource> dataSources = context.getBeansOfType(DataSource.class);
dataSources.forEach((name, dataSource) -> {
try {
// 對于HikariCP連接池
if (dataSource instanceof HikariDataSource) {
((HikariDataSource) dataSource).close();
logger.info("Closed HikariCP datasource: {}", name);
}
// 可以添加其他類型連接池的關(guān)閉邏輯
else {
// 嘗試通過反射調(diào)用close方法
Method closeMethod = dataSource.getClass().getMethod("close");
closeMethod.invoke(dataSource);
logger.info("Closed datasource: {}", name);
}
} catch (Exception e) {
logger.error("Error closing datasource: {}", name, e);
}
});
}
private static void performCustomCleanup(ApplicationContext context) {
// 這里可以添加應(yīng)用特有的清理邏輯
logger.info("Performing custom cleanup tasks");
// 例如:保存應(yīng)用狀態(tài)
// 例如:釋放本地資源
// 例如:發(fā)送關(guān)閉通知給其他系統(tǒng)
}
@Bean
public ExecutorService applicationTaskExecutor() {
return Executors.newFixedThreadPool(10);
}
@RestController
@RequestMapping("/api")
static class ApiController {
@Autowired
private ExecutorService applicationTaskExecutor;
@GetMapping("/task")
public String submitTask() {
applicationTaskExecutor.submit(() -> {
try {
logger.info("Task started, will run for 30 seconds");
Thread.sleep(30000);
logger.info("Task completed");
} catch (InterruptedException e) {
logger.info("Task interrupted");
Thread.currentThread().interrupt();
}
});
return "Task submitted";
}
}
}
優(yōu)缺點
優(yōu)點:
- 最大的靈活性和可定制性
- 可以精確控制資源關(guān)閉順序和方式
- 適用于復(fù)雜應(yīng)用場景和所有Spring Boot版本
- 可以處理Spring框架無法管理的外部資源
缺點:
- 實現(xiàn)復(fù)雜度高,需要詳細了解應(yīng)用資源
- 維護成本較高
- 容易出現(xiàn)資源關(guān)閉順序錯誤導致的問題
適用場景
- 具有復(fù)雜資源管理需求的應(yīng)用
- 需要特定順序關(guān)閉資源的場景
- 使用Spring Boot早期版本(不支持內(nèi)置優(yōu)雅停機)
- 非Web應(yīng)用或混合應(yīng)用架構(gòu)
- 使用了Spring框架不直接管理的資源(如Native資源)
方案對比和選擇指南
下面是三種方案的對比表格,幫助您選擇最適合自己場景的實現(xiàn)方式:
| 特性/方案 | SpringBoot內(nèi)置 | Actuator端點 | 自定義ShutdownHook |
|---|---|---|---|
| 實現(xiàn)復(fù)雜度 | 低 | 中 | 高 |
| 靈活性 | 低 | 中 | 高 |
| 可定制性 | 低 | 中 | 高 |
| 框架依賴 | Spring Boot 2.3+ | 任何Spring Boot版本 | 任何Java應(yīng)用 |
| 額外依賴 | 無 | Actuator | 無 |
| 觸發(fā)方式 | 系統(tǒng)信號(SIGTERM) | HTTP請求 | 系統(tǒng)信號或自定義 |
| 安全性考慮 | 低 | 高(需要保護端點) | 中 |
| 維護成本 | 低 | 中 | 高 |
| 適用Web應(yīng)用 | 最適合 | 適合 | 適合 |
| 適用非Web應(yīng)用 | 部分適合 | 部分適合 | 最適合 |
選擇SpringBoot內(nèi)置方案,如果:
- 使用Spring Boot 2.3+版本
- 主要是標準Web應(yīng)用
- 沒有特殊的資源管理需求
- 希望最簡單的配置
選擇Actuator端點方案,如果:
- 需要通過HTTP請求觸發(fā)停機
- 使用早期Spring Boot版本
- 集成了運維自動化工具
- 已經(jīng)在使用Actuator進行監(jiān)控
選擇自定義ShutdownHook方案,如果:
- 有復(fù)雜的資源管理需求
- 需要精確控制停機流程
- 使用了Spring框架不直接管理的資源
- 混合架構(gòu)應(yīng)用(既有Web又有后臺作業(yè))
結(jié)論
優(yōu)雅停機是保障應(yīng)用可靠性和用戶體驗的重要實踐。SpringBoot提供了多種實現(xiàn)方式,從簡單的配置到復(fù)雜的自定義實現(xiàn),可以滿足不同應(yīng)用場景的需求。
- 對于簡單應(yīng)用:SpringBoot內(nèi)置方案是最佳選擇,配置簡單,足夠滿足大多數(shù)Web應(yīng)用需求
- 對于需要遠程觸發(fā)的場景:Actuator端點提供了HTTP接口控制,便于集成運維系統(tǒng)
- 對于復(fù)雜應(yīng)用:自定義ShutdownHook提供了最大的靈活性,可以精確控制資源釋放順序和方式
無論選擇哪種方式,優(yōu)雅停機都應(yīng)該成為微服務(wù)設(shè)計的標準實踐。正確實現(xiàn)優(yōu)雅停機,不僅能提升系統(tǒng)穩(wěn)定性,還能改善用戶體驗,減少因應(yīng)用重啟或降級帶來的業(yè)務(wù)中斷。
以上就是SpringBoot實現(xiàn)優(yōu)雅停機的三種方式的詳細內(nèi)容,更多關(guān)于SpringBoot優(yōu)雅停機方式的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于HttpServletResponse 相關(guān)常用方法的應(yīng)用
本篇文章小編為大家介紹,基于HttpServletResponse 相關(guān)常用方法的應(yīng)用,需要的朋友參考下2013-04-04
Spring Boot 實現(xiàn)https ssl免密登錄(X.509 pki登錄)
這篇文章主要介紹了Spring Boot 實現(xiàn)https ssl免密登錄(X.509 pki登錄),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-01-01
Java concurrency之公平鎖(二)_動力節(jié)點Java學院整理
這篇文章主要為大家詳細介紹了Java concurrency之公平鎖的第二篇內(nèi)容,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06

