SpringBoot實現(xiàn)優(yōu)雅停機的多種方式
優(yōu)雅停機在微服務架構中的角色
想象一下,你在一家餐廳用餐,突然服務員走過來說:“很抱歉,我得下班了,請您盡快結賬離開。”你可能剛剛點了主菜,或許正在等著甜點,結果這個突如其來的通知讓你感到非常不滿。你的就餐體驗被打斷了,甚至你的賬單也可能出現(xiàn)混亂。與此同時,餐廳的運營也可能因此變得不順暢,服務員離開后,未完成的訂單和客戶的需求都可能無法及時得到處理,產生了不必要的麻煩。
在微服務架構中,這種情況的對應場景是應用程序的突發(fā)關閉。假設一個用戶正在發(fā)起一個請求,這個請求正在進行中時,某個微服務卻突然被強制關閉,或者在維護過程中突然停止。這就會導致用戶的操作被中斷,未處理的請求可能丟失,甚至產生數(shù)據(jù)不一致或錯誤的狀態(tài)。
這種場景不僅會讓用戶體驗變差,還可能給后端系統(tǒng)帶來極大的問題。例如,用戶信息可能未能成功保存到數(shù)據(jù)庫中,或者訂單狀態(tài)沒有被正確更新,最終造成數(shù)據(jù)的“臟”或不一致。為了避免這種問題,優(yōu)雅停機機制在現(xiàn)代微服務系統(tǒng)中變得至關重要。它確保在服務關閉時,所有進行中的請求能夠得到適當?shù)奶幚恚嚓P資源得以順利釋放,避免因應用程序的突然關閉而導致的問題。
1、什么是優(yōu)雅停機
簡單來說,優(yōu)雅停機就是指當我們需要關閉一個服務時,服務能夠有序地完成當前的工作并停止。 具體來說,優(yōu)雅停機包括以下幾個步驟:
- 停止接收新請求:當系統(tǒng)開始關閉時,需要通知負載均衡器或網關,告知它不要再將新的請求發(fā)送到即將下線的實例。
- 處理正在進行的請求:對于已經到達并正在處理的請求,系統(tǒng)要給它們完成的機會,不會突然中斷。
- 釋放資源:像數(shù)據(jù)庫連接池、線程池等資源需要被安全釋放,避免資源泄露。
- 持久化臨時數(shù)據(jù):如果有必要,系統(tǒng)會保存當前的狀態(tài)到數(shù)據(jù)庫或文件,以便下次啟動時可以恢復。
2、為什么需要優(yōu)雅停機
- 部署新版本時平穩(wěn)過渡:當我們需要更新應用時,優(yōu)雅停機可以讓舊版本服務平穩(wěn)關閉,避免突然的停機對用戶造成影響。
- 避免資源泄露:不管是內存、數(shù)據(jù)庫連接,還是線程池資源,都需要在關閉時釋放,否則就可能導致內存泄漏等問題。
- 確保數(shù)據(jù)一致性:如果有正在處理的事務,優(yōu)雅停機可以讓這些事務有機會完成,避免數(shù)據(jù)丟失或者不一致。
3、優(yōu)雅停機的實際應用場景
服務更新: 在系統(tǒng)版本升級時,通過優(yōu)雅停機完成請求處理和資源釋放,避免對用戶造成干擾。
流量調控: 在高并發(fā)場景下,如果需要暫時下線部分服務節(jié)點,優(yōu)雅停機可以幫助實現(xiàn)“無感”遷移。
訂單處理: 如出租車平臺,在訂單完成后再下線服務,避免出現(xiàn)
“中途被拋棄”的情況。
4、優(yōu)雅停機可能失效的情況
- 強制關閉:使用
kill -9強制終止進程將導致優(yōu)雅停機機制無法觸發(fā)。 - 資源耗盡:系統(tǒng)資源不足可能導致清理操作無法完成。
- 未配置超時:如果未配置超時時間,處理長時間任務可能導致停機時間過長。
5、如何在 Spring Boot 中實現(xiàn)優(yōu)雅停機
Spring Boot 優(yōu)雅停機的基礎實現(xiàn)
- 從
Spring Boot 2.3開始,優(yōu)雅停機的支持更加簡單和強大。通過設置server.shutdown配置,可以決定應用停機時的行為。
立即停機模式
- 在立即停機模式下,應用會立刻中斷所有請求和任務。
server: shutdown: immediate
雖然簡單高效,但這種方式通常只適用于測試或無狀態(tài)服務。
優(yōu)雅停機模式
- 在優(yōu)雅停機模式下,
Spring Boot會等待當前的處理任務完成,再進行停機操作。
server: shutdown: graceful
注意: 該模式下的默認等待時間為 30 秒,可通過
spring.lifecycle.timeout-per-shutdown-phase進行配置。
添加 spring-boot-starter-actuator 依賴
首先,需要確保你的項目中包含了 spring-boot-starter-actuator 依賴,這是啟用 Spring Boot 內置監(jiān)控和管理端點的工具包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
啟用 shutdown 端點
默認情況下,Spring Boot 的 shutdown 端點是禁用的。我們需要在 application.properties 或 application.yml 中顯式啟用它。
對于 application.properties:
management.endpoint.shutdown.enabled=true management.endpoints.web.exposure.include=shutdown
或者,如果你使用 application.yml:
management:
endpoint:
shutdown:
enabled: true
endpoints:
web:
exposure:
include: "shutdown"
觸發(fā)優(yōu)雅停機
配置好后,你可以通過發(fā)送 HTTP 請求來觸發(fā)優(yōu)雅停機。例如,使用 curl 命令:
curl -X POST http://localhost:8080/actuator/shutdown
當你調用這個端點時,Spring Boot 應用會停止接收新的請求,繼續(xù)處理已經收到的請求,直到所有請求處理完畢后,應用才會退出。
6、通過 ApplicationListener 接口實現(xiàn)清理邏輯
為了在應用關閉時執(zhí)行特定的清理操作(例如關閉數(shù)據(jù)庫連接、釋放資源等),你可以實現(xiàn) ApplicationListener<ContextClosedEvent> 接口。
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component;
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
// 執(zhí)行必要的清理操作
System.out.println("Starting graceful shutdown...");
try {
Thread.sleep(5000); // 模擬清理任務
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Graceful shutdown completed.");
}
}
在這個例子中,我們模擬了一個清理操作的過程(通過 Thread.sleep() 來等待)。實際上,你可以替換這部分邏輯,比如關閉數(shù)據(jù)庫連接、釋放文件句柄等。
7、使用 JVM 的鉤子函數(shù)
Java 提供了 Runtime.addShutdownHook() 方法,可以注冊一個線程,在 JVM 終止時執(zhí)行清理任務。這個方法適用于一些更底層的清理操作,尤其是在某些情況下,ApplicationListener 可能沒有機會被觸發(fā)時。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MainApp {
public static void main(String[] args) {
// 注冊關閉鉤子
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("JVM is shutting down, executing cleanup...");
try {
Thread.sleep(5000); // 模擬清理任務
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Cleanup completed.");
}));
SpringApplication.run(MainApp.class, args);
}
}
需要注意的是,JVM 關閉鉤子并不總是能執(zhí)行,尤其是在遇到強制停止(如 kill -9)時。因此,它應該作為一種補充機制,而不是唯一的保證。
8、觸發(fā)優(yōu)雅停機的方式
除了通過 /actuator/shutdown 端點來觸發(fā)優(yōu)雅停機外,還有一些常見的方法:
SIGTERM 信號:通過發(fā)送
SIGTERM信號(例如使用kill命令)可以觸發(fā)JVM的正常退出流程。
kill <pid>
這里的 <pid> 是應用的進程 ID。
kill -9 pid可以模擬了一次系統(tǒng)宕機,系統(tǒng)斷電等極端情況,而kill -15 pid則是等待應用關閉,執(zhí)行阻塞操作,有時候也會出現(xiàn)無法關閉應用的情況
#查看jvm進程pid jps #列出所有信號名稱 kill -l # Windows下信號常量值 # 簡稱 全稱 數(shù)值 # INT SIGINT 2 Ctrl+C中斷 # ILL SIGILL 4 非法指令 # FPE SIGFPE 8 floating point exception(浮點異常) # SEGV SIGSEGV 11 segment violation(段錯誤) # TERM SIGTERM 5 Software termination signal from kill(Kill發(fā)出的軟件終止) # BREAK SIGBREAK 21 Ctrl-Break sequence(Ctrl+Break中斷) # ABRT SIGABRT 22 abnormal termination triggered by abort call(Abort) #linux信號常量值 # 簡稱 全稱 數(shù)值 # HUP SIGHUP 1 終端斷線 # INT SIGINT 2 中斷(同 Ctrl + C) # QUIT SIGQUIT 3 退出(同 Ctrl + \) # KILL SIGKILL 9 強制終止 # TERM SIGTERM 15 終止 # CONT SIGCONT 18 繼續(xù)(與STOP相反, fg/bg命令) # STOP SIGSTOP 19 暫停(同 Ctrl + Z) #.... #可以理解為操作系統(tǒng)從內核級別強行殺死某個進程 kill -9 pid #理解為發(fā)送一個通知,等待應用主動關閉 kill -15 pid #也支持信號常量值全稱或簡寫(就是去掉SIG后) kill -l KILL
JVM 工具:Java 提供了一些工具(如 jcmd 和 jconsole)可以用來控制 JVM 的生命周期。例如,使用 jcmd 來發(fā)送退出命令:
jcmd <pid> VM.exit
容器平臺:如果你的應用運行在
Kubernetes或Docker等容器平臺上,平臺通常會在刪除容器時發(fā)送SIGTERM信號,并等待一段時間讓應用完成工作。
其他方法
在 Spring Boot 中實現(xiàn)優(yōu)雅停機(Graceful Shutdown)除了通過 spring-boot-actuator、ApplicationListener<ContextClosedEvent> 和 JVM 鉤子等方式外,實際上還有一些其他方法可以幫助我們實現(xiàn)優(yōu)雅停機。以下是幾種不同的實現(xiàn)方式,并配合實際應用場景和代碼示例。
1. 使用 @PreDestroy 注解
@PreDestroy 注解是 Java EE 中的一種注解,用來在 Bean 銷毀之前執(zhí)行清理任務。Spring 也支持這個注解,當 Spring 容器關閉時,所有帶有 @PreDestroy 注解的方法都會被調用。它通常用于執(zhí)行資源的釋放操作,如關閉數(shù)據(jù)庫連接池、清理緩存等。
代碼示例:
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component
public class GracefulShutdownService {
@PreDestroy
public void onShutdown() {
System.out.println("Performing cleanup before shutdown...");
// 在此處執(zhí)行資源清理,如關閉數(shù)據(jù)庫連接、釋放線程池等
}
}
適用場景:
- 適用于需要在應用關閉時清理資源的場景,尤其是在資源管理方面(例如關閉連接池、清理緩存等)。
- 在
Web應用中,通常用來釋放與外部系統(tǒng)(如數(shù)據(jù)庫、消息隊列等)的連接。
2. 使用 DisposableBean 接口
Spring 提供了 DisposableBean 接口,用于定義 Bean 在銷毀時需要執(zhí)行的清理操作。它的 destroy() 方法會在 Spring 容器關閉時被自動調用。
代碼示例:
啟用 Shutdown Hook
Spring Boot 默認會通過 JVM 的 Shutdown Hook 觸發(fā)優(yōu)雅停機。確保以下配置啟用:
spring:
main:
register-shutdown-hook: true
自定義資源釋放邏輯
如果需要在停機時執(zhí)行特定的清理操作,比如關閉數(shù)據(jù)庫連接或停止線程池,可以通過添加 Shutdown Hook 或實現(xiàn) DisposableBean 接口。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
@Component
public class GracefulShutdownService implements DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("Graceful shutdown - Cleaning up resources...");
// 執(zhí)行清理操作
}
}
或者直接通過 JVM 鉤子實現(xiàn):
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("執(zhí)行自定義的資源清理邏輯");
}));
超時機制
避免因某些請求耗時過長導致系統(tǒng)停機過程被阻塞,可以通過以下配置設置超時時間:
spring:
lifecycle:
timeout-per-shutdown-phase: 20s # 默認30秒
適用場景:
- 與
@PreDestroy類似,但DisposableBean提供了一種更明確的方式來處理Spring容器中的Bean銷毀。 - 適用于需要進行自定義清理操作(如關閉連接池、停止后臺線程等)的場景。
3. 配合自定義線程池實現(xiàn)優(yōu)雅停機
如果你的應用使用了自定義線程池,想確保所有線程在應用關閉時能夠有序停止,可以通過設置線程池的 shutdown 或 shutdownNow 方法來實現(xiàn)。
Spring Boot 提供了多種方式來創(chuàng)建和配置線程池,如 TaskExecutor、@Async 等。如果應用在停止時需要等待線程池中的任務完成,可以通過以下方式進行配置。
代碼示例:
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
@Component
public class GracefulShutdownListener implements ApplicationListener<ContextClosedEvent> {
private final ThreadPoolTaskExecutor taskExecutor;
public GracefulShutdownListener(ThreadPoolTaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
System.out.println("Shutting down thread pool...");
// 給當前線程池中的任務一些時間來完成
taskExecutor.shutdown();
try {
// 等待任務完成
if (!taskExecutor.getThreadPoolExecutor().awaitTermination(60, TimeUnit.SECONDS)) {
System.out.println("Timeout reached, forcing shutdown...");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
適用場景:
- 適用于你的應用需要執(zhí)行異步任務時,并希望確保這些任務能夠有序地完成,防止強制中斷。
- 適用于后臺線程池或異步任務系統(tǒng),確保應用關閉時,任務能夠平穩(wěn)終止。
4. 在 Kubernetes 中實現(xiàn)優(yōu)雅停機
如果你將 Spring Boot 應用部署在容器編排平臺(如 Kubernetes)上,Kubernetes 會自動幫助你實現(xiàn)優(yōu)雅停機。Kubernetes 發(fā)送 SIGTERM 信號,并等待容器停止一定時間(通常是 30 秒),在這段時間內,應用應當完成正在進行的請求并清理資源。
你可以通過設置 terminationGracePeriodSeconds 來配置應用在收到 SIGTERM 信號后的最大優(yōu)雅停機時間。
配置示例(Kubernetes):
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
template:
spec:
containers:
- name: my-app
image: my-app-image
ports:
- containerPort: 8080
terminationGracePeriodSeconds: 60 # 等待 60 秒
適用場景:
- 當應用部署在
Kubernetes或Docker Swarm等容器平臺時,這種方式能夠自動觸發(fā)優(yōu)雅停機,配合應用的優(yōu)雅停機策略(如通過 Actuator 觸發(fā) shutdown)。 - 適用于容器化部署和云原生應用,能夠與平臺的生命周期管理機制配合。
5. 使用自定義 shutdown 信號處理器
如果你不想完全依賴 Spring 提供的機制,可以實現(xiàn)一個自定義的信號處理器來捕獲和響應關閉信號。通過這種方式,你可以更精細地控制停機流程。
代碼示例:
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class ShutdownSignalHandler {
public ShutdownSignalHandler() throws IOException {
// 注冊 SIGTERM 信號處理器
Runtime.getRuntime().addShutdownHook(new Thread(this::shutdown));
}
private void shutdown() {
System.out.println("Received shutdown signal. Performing graceful shutdown...");
// 執(zhí)行清理操作,如關閉連接池、停止后臺服務等
}
}
適用場景:
- 如果你需要更靈活的優(yōu)雅停機控制,可以使用自定義的信號處理器。
- 適用于需要處理各種不同信號(如
SIGTERM、SIGINT等)的復雜場景,特別是對于非 Spring 的基礎設施組件。
9、小結
優(yōu)雅停機是 Spring Boot 應用的重要特性,它幫助我們確保在關閉應用時能夠平穩(wěn)地釋放資源,處理完正在進行的請求,從而提高系統(tǒng)的穩(wěn)定性和可靠性。通過 Spring Boot Actuator、ApplicationListener 接口和 JVM 鉤子函數(shù)等多種方式,我們可以確保應用程序能夠安全、順利地關閉,而不會影響用戶體驗或導致數(shù)據(jù)丟失。
除了 Spring Boot Actuator 和 ApplicationListener 外,還有多種方式可以實現(xiàn)優(yōu)雅停機。每種方式有不同的適用場景:
@PreDestroy和DisposableBean:適用于簡單的資源釋放和清理操作。- 自定義線程池清理:適用于需要確保線程池任務完成的場景。
- 容器平臺的優(yōu)雅停機:適用于容器化應用,
Kubernetes會自動管理服務的優(yōu)雅停機。 - 自定義信號處理:適用于需要更靈活、底層控制的停機過程。
根據(jù)應用的需求,你可以選擇合適的方式實現(xiàn)優(yōu)雅停機,從而提高系統(tǒng)的可靠性和用戶體驗。
以上就是SpringBoot實現(xiàn)優(yōu)雅停機的多種方式的詳細內容,更多關于SpringBoot優(yōu)雅停機的資料請關注腳本之家其它相關文章!
相關文章
解決Mybatis中foreach嵌套使用if標簽對象取值的問題
這篇文章主要介紹了解決Mybatis中foreach嵌套使用if標簽對象取值的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
Mybatis-Plus根據(jù)自定義注解實現(xiàn)自動加解密的示例代碼
我們把數(shù)據(jù)存到數(shù)據(jù)庫的時候,有些敏感字段是需要加密的,從數(shù)據(jù)庫查出來再進行解密,如果我們使用的是Mybatis框架,那就跟著一起探索下如何使用框架的攔截器功能實現(xiàn)自動加解密吧,需要的朋友可以參考下2024-06-06
解決springboot項目找不到resources目錄下的資源問題
這篇文章主要介紹了解決springboot項目找不到resources目錄下的資源問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
idea使用帶provide修飾依賴導致ClassNotFound
程序打包到Linux上運行時,若Linux上也有這些依賴,為了在Linux上運行時避免依賴沖突,可以使用provide修飾,本文主要介紹了idea使用帶provide修飾依賴導致ClassNotFound,下面就來介紹一下解決方法,感興趣的可以了解一下2024-01-01
Java實現(xiàn)PDF轉Word的示例代碼(無水印無頁數(shù)限制)
這篇文章主要為大家詳細介紹了如何利用Java語言實現(xiàn)PDF轉Word文件的效果,并可以無水印、無頁數(shù)限制。文中的示例代碼講解詳細,需要的可以參考一下2022-05-05

