Spring boot 2.0 之优雅停机
2018.05.20 18:41* 字数 1794 阅读 2638评论 0喜欢 22
spring boot 框架在生产环境使用的有一段时间了,它“约定大于配置”的特性,体现了优雅流畅的开发过程,它的部署启动方式(java -jar xxx.jar)也很优雅。但是我使用的停止应用的方式是 kill -9 进程号,即使写了脚本,还是显得有些粗鲁。这样的应用停止方式,在停止的那一霎那,应用中正在处理的业务逻辑会被中断,导致产生业务异常情形。这种情况如何避免,本文介绍的优雅停机,将完美解决该问题。
00 前言
什么叫优雅停机?简单说就是,在对应用进程发送停止指令之后,能保证正在执行的业务操作不受影响。应用接收到停止指令之后的步骤应该是,停止接收访问请求,等待已经接收到的请求处理完成,并能成功返回,这时才真正停止应用。
这种完美的应用停止方式如何实现呢?就Java语言生态来说,底层的技术是支持的,所以我们才能实现在Java语言之上的各个web容器的优雅停机。
在普通的外置的tomcat中,有shutdown脚本提供优雅的停机机制,但是我们在使用Spring boot的过程中发现web容器都是内置(当然也可使用外置,但是不推荐),这种方式提供简单的应用启动方式,方便的管理机制,非常适用于微服务应用中,但是默认没有提供优雅停机的方式。这也是本文探索这个问题的根本原因。
应用是否是实现了优雅停机,如何才能验证呢?这需要一个处理时间较长的业务逻辑,模拟这样的逻辑应该很简单,使用线程sleep或者长时间循环。我的模拟业务逻辑代码如下:
@GetMapping(value = "/sleep/one", produces = "application/json")
public ResultEntity 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 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的执行完成