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