龙空技术网

杀进程导致任务中断,数据丢失?1个技巧让SpringBoot延迟停机

萌新程序员成长日记 3100

前言:

如今看官们对“java任务调度把进程停了会对数据有影响吗”都比较注重,看官们都需要了解一些“java任务调度把进程停了会对数据有影响吗”的相关文章。那么小编在网摘上网罗了一些关于“java任务调度把进程停了会对数据有影响吗””的相关文章,希望你们能喜欢,咱们快快来了解一下吧!

前言

今天公司项目要发新版本,经理把这个重任交到了我的手上。

发版过程一切顺利,没想到新版发布后出现了数据丢失的情况,到底怎么回事?我要怎么解决这个问题?

目录杀进程会导致定时任务中断吗?网上优雅停机的方法有用吗?自己动手实现延时停机杀进程会导致定时任务中断吗?

我们项目部署在Linux系统上,经过一番折腾终于成功连上服务器,网上查了一下发布项目的主要步骤(只保留重点,已省略其他不相关的操作):

使用ps命令找到项目运行的进程编号(PID);使用kill命令干掉该进程;上传并替换最新JAR包;执行nohup java -jar命令启动项目;

我按着这个步骤就是一顿操作,还好中间没出啥问题顺利上线。(心想发版原来如此简单~)

没过多久,业务部门反应后台报表统计数据有缺失,经理让我协助排查一下。

我打开报表一看,确实存在部分数据丢失的情况,看时间正好是我发布新版本的时间,不会又是我的锅吧?(有点慌~)

怎么排查问题呢?

先来看一下报表数据是怎么统计的,我找到代码一看,发现数据是通过Spring的定时任务统计的。

为了方便理解,我用代码模拟一下:

整个业务代码看下来没找到问题。

会不会是定时任务没执行?

查日志看执行记录发现缺失数据的那个时间点定时任务执行过,但是没有执行结束的日志。

看到这里我好像突然想到了数据丢失的原因:

当定时任务执行到一半时,正好我使用kill命令直接强行干掉了应用进程,定时任务来不及保存数据就结束了。

模拟一下我的猜想,定时任务还没执行完毕应用进程就结束了:

定时任务强行被中断导致数据丢失,怎么解决这个问题呢?

网上优雅停机的方法有用吗?

按照我的设想,在执行停机操作时,如果有定时任务正在运行,那么程序应该等待任务执行完毕再停机,姑且称这个操作为“延迟停机”吧。

那有没有办法能实现我所设想的延迟停机呢?

一番搜索,发现网上挺多所谓的实现优雅停机的教程,好像挺符合我的要求,先测试一下吧。

首先在pom.xml文件中引入spring-boot-starter-actuator依赖:

接着在配置文件中加上2个属性:

按照教程所说,当我们想关闭应用时只需请求/actuator/shutdown路径就可实现优雅停机。

可惜测试结果让我很失望,我在定时任务执行到一半时请求/actuator/shutdown路径,此时程序依然直接就中断了,并没有等到定时任务执行结束。

看来这所谓的优雅停机并不能实现我的要求,网上真的太多复制粘贴的教程了,看着心累!

看来只能去找找官方文档了,官方对shutdown端点的解释是:让应用可以优雅的被关闭。

按照这个解释,Spring Boot官方应该是支持优雅停机的。

不过后来我在github上Spring Boot开源项目中发现一个关于优雅停机的issue-4657:

这个issue是2015年12月提出的,下面有非常多的人讨论如何让Spring Boot应用优雅的停机,但是几年过去了这个问题一直没有被解决,直到最近这个问题状态才被改为closed并且加入了2.3.0.M3的milestone,不知道什么时候能发布2.3版本(目前Spring Boot最新版本是2.2.6)。

自己动手实现延时停机

虽然官方暂时没有给出优雅停机的解决方案,但是该issue下面的一些讨论倒是给了我一些启发,最终我自己尝试实现了一种延迟停机的方案。

首先来看一下效果:

整个流程如下:

定时任务开始执行后我发出停机指令;检测到有定时任务正在执行,程序选择暂停执行停机指令,等待任务完成;定时任务执行完毕,程序继续执行停机指令完成停机;

从结果可以看到,我发出停机指令后程序并没有立即执行停机操作,而是延迟到定时任务执行完毕才停机,完美的实现了我的需求。

那么我是怎么实现延迟停机的呢?

原理介绍

首先介绍一下Spring Context模块中的ApplicationListener接口:

这个接口有什么用?

它里面有一个onApplicationEvent方法可以监听到容器的一些特定事件,比如说当容器发生启用、刷新、关闭以及停止事件时,系统会回调onApplicationEvent方法。

我们先来理一下整个停机流程:

首先我发出停机命令,JVM在收到停机指令后会通知Spring Boot应用关闭容器,关闭容器则会触发上面ApplicationListener接口中的onApplicationEvent回调。

在回调方法里我们有机会在正式停机前执行一段自定义的逻辑,我也正是通过这个回调方法实现延迟停机的。

实现步骤

定义一个类实现ApplicationListener接口:

首先申明这个类只接收ContextClosedEvent(容器关闭)事件回调;

其次从容器中获取定时任务的调度器ThreadPoolTaskScheduler,我为什么知道Spring定时任务的默认调度器是ThreadPoolTaskScheduler?那是因为我在文章为什么定时任务到时间不执行?带你深入源码找答案里有分析过定时任务源码;

接着从调度器中获取执行任务的线程池ScheduledThreadPoolExecutor;

最后我通过while循环判断线程池的awaitTermination方法阻塞停机操作,等待线程池里执行定时任务的线程结束后,再最终关闭线程池;

“分享知识,收获快乐”

我是一名程序员,喜欢我的文章欢迎 关注 及 转发,我会经常与大家分享工作当中的实用技巧与经验。

标签: #java任务调度把进程停了会对数据有影响吗