00 前言
什么叫优雅停机?简单说就是,在对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响。应用接收到停止指令之后的步骤应该是,停止接收访问请求,等待已经接收到的请求处理完成,并能成功返回,这时才真正停止应用。
这种完美的应用停止方式如何实现呢?就Java语言生态来说,底层的技术是支持的,所以我们才能实现在Java语言之上的各个web容器的优雅停机。
在普通的外置的tomcat中,有shutdown脚本提供优雅的停机机制,但是我们在使用Spring boot的过程中发现web容器都是内置(当然也可使用外置,但是不推荐),这种方式提供简单的应用启动方式,方便的管理机制,非常适用于微服务应用中,但是默认没有提供优雅停机的方式。这也是本文探索这个问题的根本原因。
应用是否是实现了优雅停机,如何才能验证呢?这需要一个处理时间较长的业务逻辑,模拟这样的逻辑应该很简单,使用线程sleep或者长时间循环。我的模拟业务逻辑代码如下:
@GetMapping(value = "/sleep/one", produces = "application/json")
public ResultEntity<Long> sleepOne(String systemNo){
logger.info("模拟业务处理1分钟,请求参数:{}", systemNo);
Long serverTime = System.currentTimeMillis();
// try {
// Thread.sleep(60*1000L);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
while (System.currentTimeMillis() < serverTime + (60 * 1000)){
logger.info("正在处理业务,当前时间:{},开始时间:{}", System.currentTimeMillis(), serverTime);
}
ResultEntity<Long> resultEntity = new ResultEntity<>(serverTime);
logger.info("模拟业务处理1分钟,响应参数:{}", resultEntity);
return resultEntity;
}
验证方式就是,在触发这个接口的业务处理之后,业务逻辑处理时间长达1分钟,需要在处理结束前,发起停止指令,验证是否能够正常返回。验证时所使用的kill指令:kill -2(Ctrl + C)、kill -15、kill -9。
01 Java 语言的优雅停机
从上面的介绍中我们发现,Java语言本身是支持优雅停机的,这里就先介绍一下普通的java应用是如何实现优雅停止的。
当我们使用kill PID的方式结束一个Java应用的时候,JVM会收到一个停止信号,然后执行shutdownHook的线程。一个实现示例如下:
public class ShutdownHook extends Thread {
private Thread mainThread;
private boolean shutDownSignalReceived;
@Override
public void run() {
System.out.println("Shut down signal received.");
this.shutDownSignalReceived=true;
mainThread.interrupt();
try {
mainThread.join(); //当收到停止信号时,等待mainThread的执行完成
} catch (InterruptedException e) {
}
System.out.println("Shut down complete.");
}
public ShutdownHook(Thread mainThread) {
super();
this.mainThread = mainThread;
this.shutDownSignalReceived = false;
Runtime.getRuntime().addShutdownHook(this);
}
public boolean shouldShutDown(){
return shutDownSignalReceived;
}
}
其中关键语句Runtime.getRuntime().addShutdownHook(this);,注册一个JVM关闭的钩子,这个钩子可以在以下几种场景被调用:
程序正常退出
使用System.exit()
终端使用Ctrl+C触发的中断
系统关闭
使用Kill pid命令干掉进程
02 vertx的优雅停机
vertx本身的close方法已经实现了他的优雅停机,我们只需保证java进程在vertx close结束后关闭即可,所以代码如下:
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("start stop vertx");
Vertx vertx= SpringContextUtils.getBean(Vertx.class);
CountDownLatch countDownLatch=new CountDownLatch(1);
vertx.close(res->{
countDownLatch.countDown();
});
try {
countDownLatch.await(30, TimeUnit.SECONDS);
log.info("stop vertx success");
} catch (InterruptedException e) {
e.printStackTrace();
}
}));