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

参考

posted @ 2021-12-09 21:52  MaxStack  阅读(1978)  评论(0编辑  收藏  举报