Java优雅停机指南
文章目录
- 背景
- 需解决的问题
- 优雅停机指南
- 问题一:如何解决停机时还有流量打到被停机服务上
- 问题二:解决其他线程例如定时任务停机导致线程中断,任务未执行完成
- 方法一:监听事件`ContextClosedEvent`
- 方法二:实现`SmartLifecycle`接口
- 方法三:使用@PreDestroy注解
- 方法四: 注册`ShutdownHandler`
- 方法五:注册至JVM的关闭钩子
背景
- 为避免服务在停机过程中请求(业务)丢失,包含停机过程中继续有请求进来,或正在运行中的任务被强制中断。需规范各子系统的服务关机流程,实现优雅关机,避免业务中断。
需解决的问题
- 解决停机时还有流量打到被停机实例上
- 解决其他线程例如定时任务停机导致线程中断,任务未执行完成的问题。
优雅停机指南
- 进行优雅停机需使用kill -15命令关停服务
问题一:如何解决停机时还有流量打到被停机服务上
- Spring原生已有配置可解决这类问题,配置如下:
server:shutdown: graceful ###开启优雅停止容器
spring:lifecycle:timeout-per-shutdown-phase: 30s ###优雅关闭容器时最长等待时间,默认30s,可根据实际情况加长时间
使用效果:
- 已经到达服务的请求会继续处理。
- 后续请求该停机实例的请求会直接拒绝。
- 达到配置的最大时间,即使还有未处理完成的请求,也会直接关闭容器。
问题二:解决其他线程例如定时任务停机导致线程中断,任务未执行完成
-
可以通过增加
jvm
钩子等方法,在关闭时对正在运行的任务进行检测,如未完成可以继续任务或者进行清理工作。 -
针对本问题,将会给出几种增加关闭spring时触发钩子的写法,优先级从高到低(除方法五),优先级高的写法会优先执行。
方法一:监听事件ContextClosedEvent
-
使用spring的事件监听机制,监听
ApplicationContext
的关闭事件。 -
此种方法优先级最高。会在关闭流程中最先执行。
例:
@Component
public class CloseEventDemo implements ApplicationListener<ContextClosedEvent> {@Overridepublic void onApplicationEvent(ContextClosedEvent event) {try {LogUtils.info("######################## TestCloseEvent start");//业务处理逻辑LogUtils.info("######################## TestCloseEvent end");} catch (InterruptedException e) {LogUtils.info("######################## TestCloseEvent error");throw new RuntimeException(e);}}
}
方法二:实现SmartLifecycle
接口
-
Spring提供的
SmartLifecycle
包含在Context创建和关闭时都会执行。在关闭时,会在监听ContextClosedEvent
事件的代码执行后按照顺序执行。 -
可以通过实现
getPhase
方法控制顺序,启动时按照由小到大执行,关闭时由大到小执行。 -
Spring关闭容器(如tomcat等)就是通过
SmartLifecycle
关闭的,可参考WebServerGracefulShutdownLifecycle
,此lifecycle设置的顺序为int类型的最大值,也就是关闭时第一个执行。
例:
@Component
public class LifecycleHookDemo implements SmartLifecycle {private volatile boolean running = false;@Overridepublic void start() {running = true;LogUtils.info("#####应用启动/重启后执行初始化...");}@Overridepublic void stop() {running = false;LogUtils.info("#######应用停止/重启前执行清理...");}@Overridepublic boolean isRunning() {return running;}// 设置phase值控制执行顺序(启动时由小到大,关闭时由大到小)@Overridepublic int getPhase() {return SmartLifecycle.DEFAULT_PHASE;}
}
方法三:使用@PreDestroy注解
- 关闭时,spring会调用标记
@PreDestroy
注解的方法,执行顺序在SmartLifecycle
之后。
例:
@Component
public class PreDestroyDemo {@PreDestroypublic void destroy() {LogUtils.info("######################## PreDestroyDemo destroy");}}
方法四: 注册ShutdownHandler
- Spring允许注册Runnable到Spring的
shutdownHook
,执行顺序最后,Logback
就是用过这种方式去做最后的日志工作。
例:
@Component
public class ShutdownHandlerDemo implements ApplicationListener<ApplicationPreparedEvent> {@Overridepublic void onApplicationEvent(ApplicationPreparedEvent event) {//注册至spring的ShutdownHandlers,关闭时会执行SpringApplication.getShutdownHandlers().add(new ShutdownHandler());}private static class ShutdownHandler implements Runnable {@Overridepublic void run() {LogUtils.info("############ShutdownHandlerDemo.ShutdownHandler.run()");}}
}
方法五:注册至JVM的关闭钩子
- 方法一至方法四本质是执行在Spring注册到JVM的
shutdownHook
中执行的,JVM可以挂载多个shutdownHook
,调用顺序按照先进后出,但是多个shutdownHook
并行执行.
例:
@Component
public class JavaShutdownHookDemo implements ApplicationListener<ApplicationPreparedEvent>, Runnable {@Overridepublic void onApplicationEvent(ApplicationPreparedEvent event) {//向JVM注册shutdownHookRuntime.getRuntime().addShutdownHook(new Thread(this, "JavaShutdownHookDemo"));}@Overridepublic void run() {LogUtils.info("######################## JavaShutdownHookDemo start");}
}