Spring Boot 優雅退出機制


問題

最近項目重構,改用 Spring Boot 框架,遇到個問題:當程序 catch 住某些 exception ,需要停掉整個 application ,然后人工介入查看。但是,發現沒有辦法停掉應用,應用本身也不繼續跑下去,它就 hang 在那了。報錯如下:

o.s.c.s.DefaultLifecycleProcessor.stop(387) - Failed to shut down 1 bean with phase value 2147483647 within timeout of 30000ms: [messageListenerContainer]

調查

定位到相關代碼塊如下:

public void onMessage(Message msg) {
	try {
		processMsg(msg);
	} catch (Throwable t) {
		System.exit(-1);
	}
}

收到了JMS消息,但是processMsg報錯,然后會被 catch 住,然后執行System.exit,但是失敗了。

這個DefaultLifecycleProcessor是 springframework 的類,在程序 shutdown 的時候會被調用來銷毀/關閉 bean 。

分析

結合 jstack.review 查看 thread dump 如下:

PNG

沒有死鎖,進一步分析可知,SpringContextShutdownHook想要 shutdown messageListenerContainer,但是后者還在等消息。所以前者 timeout 了。

解決1

最簡單的方案,退出時不要調用 Spring 的 ShutdownHook ,就不會有后面一系列的問題。在 properties 文件加入一行配置:

spring.main.register-shutdown-hook=false

這個方案足夠簡單,也奏效。但是沒有合理地關閉資源,可能會造成資源浪費。如果頻繁啟停應用,可能會有問題。

解決2

這個問題的本質是,處理消息的線程不能自己關閉自己的 JMS container 。

那么,就建一個 monitor 線程,如果需要退出時,發一個信號給 monitor 線程,讓它去關閉 JMS container (以及 DataSource, File, etc.) 。示例代碼如下:

Executors.newSingleThreadExecutor.execute(new Runnable() {
	public void run() {
		if (signal)
			stopTheContainer();
	}
}

這個signal,可以用一個AtomicBoolean shutdownFlag 來實現。

解決2 - 補充

再“優雅”一點,遇到異常需要退出時,拋一個自定義的 Exception,比如 ApplicationExitException,然后用一個自定義的 ErrorHandler 去接住這個異常,然后再新起線程發出退出信號。

@Service
public class ApplicationExitErrorHandler implements ErrorHandler {
    @Override
    public void handleError(Throwable t) {
      // actual exit logic
    }
}

參考 -> https://www.baeldung.com/spring-jms#error-handler

這樣做的好處是:解耦,業務代碼和異常處理代碼分離,邏輯上更加清晰。
壞處是:不熟悉 Spring Error Handling 框架的人 查代碼/debug 起來更加困難。

解決3

網上有說升級 Spring Boot version to 2.3.4.RELEASE 就能解決問題的,這個筆者沒有試過。僅僅列在這里作為一個可能的選項。

參考這里 -> https://github.com/spring-cloud/spring-cloud-gateway/issues/2037

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM