淺談springboot項(xiàng)目中定時(shí)任務(wù)如何優(yōu)雅退出
在一個(gè)springboot項(xiàng)目中需要跑定時(shí)任務(wù)處理批數(shù)據(jù)時(shí),突然有個(gè)Kill命令或者一個(gè)Ctrl+C的命令,此時(shí)我們需要當(dāng)批數(shù)據(jù)處理完畢后才允許定時(shí)任務(wù)關(guān)閉,也就是當(dāng)定時(shí)任務(wù)結(jié)束時(shí)才允許Kill命令生效。
啟動(dòng)類
啟動(dòng)類上我們獲取到相應(yīng)的上下文,捕捉相應(yīng)命令。在這里插入代碼片
@SpringBootApplication/**指定mapper對(duì)應(yīng)包的路徑*/@MapperScan('com.youlanw.kz.dao')/**開(kāi)啟計(jì)劃任務(wù)*/@EnableScheduling/**開(kāi)啟異常重試機(jī)制*/@EnableRetrypublic class YlkzTaskApplication { public static ConfigurableApplicationContext context; public static void main(String[] args) { context = SpringApplication.run(YlkzTaskApplication.class, args); /** * 捕捉命令實(shí)現(xiàn)優(yōu)雅退出 */ MySignalHandler.install('TERM'); //捕捉kill命令 MySignalHandler.install('INT'); //捕捉ctrl+c命令 }}
優(yōu)雅退出配置類
通過(guò)install方法捕捉到相應(yīng)的命令,
通過(guò)signalAction方法進(jìn)行總開(kāi)發(fā)的控制。
import org.slf4j.LoggerFactory;import sun.misc.Signal;import sun.misc.SignalHandler;/** * @description: 定時(shí)任務(wù)控制類(實(shí)現(xiàn)優(yōu)雅退出) * @method: * @author: mamengmeng * @date: 10:51 2018/8/13 */public class MySignalHandler implements SignalHandler { private final static org.slf4j.Logger logger = LoggerFactory.getLogger(MySignalHandler.class); private SignalHandler oldHandler; /** * 定時(shí)任務(wù)總開(kāi)關(guān)-狀態(tài):true:打開(kāi) false:關(guān)閉 */ public static boolean base_flag = true; @Override public void handle(Signal signal) { signalAction(signal); } public static SignalHandler install(String signalName) { Signal diagSignal = new Signal(signalName); MySignalHandler instance = new MySignalHandler(); instance.oldHandler = Signal.handle(diagSignal, instance); return instance; } public void signalAction(Signal signal) { try { //關(guān)閉總開(kāi)關(guān) this.base_flag = false; logger.info('n執(zhí)行優(yōu)雅退出操作n等待運(yùn)行中任務(wù)執(zhí)行完畢…………'); Thread.sleep(3000); StringBuffer stringBuffer = new StringBuffer('a'); //此處為相關(guān)的業(yè)務(wù)代碼,只要還有一個(gè)定時(shí)任務(wù)在執(zhí)行,那么就等待線程任務(wù)執(zhí)行完畢。 while (BaseApplyTask.apply_flag || BaseResumeTask.resume_flag || CorpDemandTask.demand_flag || RecommendResumeTask.resume_flag || BaseCodeTask.code_flag || RecommendoneTask.resume_flag ||ResumeByZcbTask.zpbresume_flag) {//等待線程任務(wù)執(zhí)行完畢stringBuffer.append(''); } //獲取到的上下文對(duì)象關(guān)閉相應(yīng)的程序。 YlkzTaskApplication.context.close(); logger.info('n================n程序已安全退出!n================'); oldHandler.handle(signal); } catch (Exception e) { logger.error('handle|Signal handler' + 'failed, reason ' + e.getMessage()); e.printStackTrace(); } }}
舉例說(shuō)明
我們?cè)诙〞r(shí)任務(wù)中添加一個(gè)總開(kāi)關(guān),當(dāng)總開(kāi)關(guān)是關(guān)著時(shí)是不允許定時(shí)任務(wù)執(zhí)行的,
@Componentpublic class BaseCodeTask { private final static Logger logger = LoggerFactory.getLogger(BaseCodeTask.class); @Autowired private ResumeService resumeService; public static boolean code_flag = true; //簡(jiǎn)歷任務(wù)執(zhí)行狀態(tài) true:執(zhí)行中 false:執(zhí)行完畢 private static final Integer LIMIT = 500; private final static long time = 60 * 1000; //一分鐘 /** * @param * @description: 同步簡(jiǎn)歷信息(定時(shí)任務(wù)) * 任務(wù)執(zhí)行間隔時(shí)間:6秒 * 待同步數(shù)據(jù)為空,則5分鐘后執(zhí)行下一次 * @method: sendResume * @author: zhengmingjie * @date: 16:17 2018/8/3 * @return: void */ @Scheduled(initialDelay = 1000, fixedDelay = time / 10) @Async public void sendResume() throws Exception { List<Resume> list = null; try { //總開(kāi)關(guān)狀態(tài):true:打開(kāi) false:關(guān)閉 if (!MySignalHandler.base_flag)return; this.code_flag = true; logger.info('n======定時(shí)任務(wù):初始化基本數(shù)據(jù)======n開(kāi)始執(zhí)行n'); //以下是業(yè)務(wù)代碼。相關(guān)的定時(shí)任務(wù)批處理 resumeService.initializationMap(); resumeService.setCodeDictionary(); resumeService.setCityInfo(); resumeService.setCodePostInfo(); logger.info('n======定時(shí)任務(wù):初始化基本數(shù)據(jù)======n結(jié)束n'); } catch (Exception e) { e.printStackTrace(); } finally { this.code_flag = false; } }}
定時(shí)任務(wù)優(yōu)雅退出的使用可以有效的防止批處理任務(wù)的中斷,小伙伴們可以嘗試添加哦。。。。
補(bǔ)充知識(shí):springboot自帶定時(shí)器實(shí)現(xiàn)定時(shí)任務(wù)的開(kāi)啟關(guān)閉以及動(dòng)態(tài)修改定時(shí)規(guī)則
最近項(xiàng)目中遇到了需要自動(dòng)定時(shí)導(dǎo)出的需求,用戶可以從頁(yè)面修改導(dǎo)出的時(shí)間規(guī)則,可以啟用和停用定時(shí)任務(wù)。
經(jīng)過(guò)了解,項(xiàng)目中目前實(shí)現(xiàn)定時(shí)任務(wù),一般有三種選擇,一是用Java自帶的timer類。稍微看了一下,可以實(shí)現(xiàn)大部分的指定頻率的任務(wù)的調(diào)度(timer.schedule()),也可以實(shí)現(xiàn)關(guān)閉和開(kāi)啟(timer.cancle)。但是用其來(lái)實(shí)現(xiàn)某天的某個(gè)時(shí)間或者某月的某一天調(diào)度任務(wù)有點(diǎn)不方便。
二是采用Quartz 調(diào)度器實(shí)現(xiàn)。這是一個(gè)功能很強(qiáng)大的開(kāi)源的專門(mén)用于定時(shí)任務(wù)調(diào)度的框架,也很好的和springboot整合,缺點(diǎn):配置復(fù)雜,需要花費(fèi)一定的時(shí)間去了解和研究。(本人懶,因此沒(méi)有選擇這個(gè),但是這個(gè)功能地區(qū)強(qiáng)大,有時(shí)間研究)
三是spring3.0以后自帶的scheduletask任務(wù)調(diào)度,可以實(shí)現(xiàn)quartz的大部分功能,不需要額外引用jar,也不需要另外配置。而且支持注解和配置文件兩種。
因此最后選擇直接用spring自帶的task 實(shí)現(xiàn)。
基本用法很簡(jiǎn)單,通過(guò)在方法上加注解@schedule(也可以通過(guò)xml文件配置的方式),注解里有 cron ,fixedDelay ,fixedRate ,initialDelay 等等參數(shù),可以完成指定時(shí)間,平率執(zhí)行此方法。這里不詳細(xì)介紹。
直接介紹,通過(guò)頁(yè)面動(dòng)態(tài)修改cron參數(shù),修改定時(shí)規(guī)則的思路。
1 實(shí)現(xiàn)接口SchedulingConfigurer,這個(gè)接口只有一個(gè)方法,配置定時(shí)任務(wù)。重寫(xiě)此方法,添加新的任務(wù)實(shí)現(xiàn)runable和新的觸發(fā) 實(shí)現(xiàn)trigger 。
2 在新的觸發(fā)里,把修改的cron寫(xiě)入新的觸發(fā)
3 寫(xiě)UI 方法,接收前端修改的定時(shí)參數(shù)。
代碼如下:
package com.fiberhome.ms.cus.cashform.ui;import java.util.Date;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.Trigger;import org.springframework.scheduling.TriggerContext;import org.springframework.scheduling.annotation.SchedulingConfigurer;import org.springframework.scheduling.config.ScheduledTaskRegistrar;import org.springframework.scheduling.support.CronTrigger;import org.springframework.stereotype.Component;@Componentpublic class DynamicScheduledTask implements SchedulingConfigurer {@Autowiredprivate ScheduleExport scheduleExport;// private static String DEFAULT_CRON = '0/10 * * * * ?';private String cron = '';public String getCron() {return cron;}public void setCron(String cron) {this.cron = cron;}@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {// TODO Auto-generated method stubtaskRegistrar.addTriggerTask(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubtry {scheduleExport.scheduleTaskExport();//異步定時(shí)生成文件System.out.println('Msg:定時(shí)生成文件成功');} catch (Exception e) {// TODO: handle exceptione.printStackTrace();System.out.println('Error:定時(shí)生成文件錯(cuò)誤');}}}, new Trigger() {@Overridepublic Date nextExecutionTime(TriggerContext triggerContext) {// TODO Auto-generated method stubif (''.equals(cron)|| cron == null)return null;CronTrigger trigger = new CronTrigger(cron);// 定時(shí)任務(wù)觸發(fā),可修改定時(shí)任務(wù)的執(zhí)行周期Date nextExecDate = trigger.nextExecutionTime(triggerContext);return nextExecDate;}});System.out.println('can?');}}
這個(gè)方法可以實(shí)現(xiàn) 根據(jù)頁(yè)面設(shè)置動(dòng)態(tài)修改定時(shí)器的cron參數(shù),不用重啟服務(wù)。但是運(yùn)行之后發(fā)現(xiàn)了一個(gè)缺陷,即必須在修改完之后,只有再一次到達(dá)定時(shí)任務(wù)的時(shí)間,才會(huì)調(diào)用新的觸發(fā)時(shí)間, 這就導(dǎo)致,頁(yè)面設(shè)置的時(shí)間并不能即時(shí)生效,這在項(xiàng)目中是不符合用戶的要求,于是為了解決這個(gè)bug,換了另外一種解決方法。
思路:(了解ThreadPoolTaskScheduler這個(gè)類,TaskScheduler接口的默認(rèn)實(shí)現(xiàn)類,多線程定時(shí)任務(wù)執(zhí)行。可以設(shè)置執(zhí)行線程池?cái)?shù)(默認(rèn)一個(gè)線程))
1、ThreadPoolTaskScheduler 實(shí)現(xiàn)TaskScheduler,可以通過(guò)方法 schedule(java.lang.Runnable task, Trigger trigger),添加定時(shí)任務(wù)和觸發(fā)器。返回java.util.concurrent.ScheduledFuture<?>,future可以控制任務(wù)的開(kāi)關(guān)等。
2、前端修改定時(shí)參數(shù),在set方法中修改ThreadPoolTaskScheduler 的觸發(fā)器。
代碼如下:
package com.fiberhome.ms.cus.cashform.ui.util;import java.util.Date;import java.util.concurrent.ScheduledFuture;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.scheduling.Trigger;import org.springframework.scheduling.TriggerContext;import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;import org.springframework.scheduling.support.CronTrigger;import org.springframework.stereotype.Component;import com.fiberhome.ms.cus.cashform.ui.ScheduleExport;@Componentpublic class DynamicScheduleTaskSecond {@Autowiredprivate ThreadPoolTaskScheduler threadPoolTaskScheduler;@Autowiredprivate ScheduleExport scheduleExport;private ScheduledFuture<?> future;private String cron = '';public String getCron() {return cron;}public void setCron(String cron) {this.cron = cron;stopCron();future = threadPoolTaskScheduler.schedule(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubtry {scheduleExport.scheduleTaskExport();// 異步定時(shí)生成文件System.out.println('Msg:定時(shí)生成文件成功');} catch (Exception e) {// TODO: handle exceptione.printStackTrace();System.out.println('Error:定時(shí)生成文件錯(cuò)誤');}}}, new Trigger() {@Overridepublic Date nextExecutionTime(TriggerContext triggerContext) {// TODO Auto-generated method stubif (''.equals(cron) || cron == null)return null;CronTrigger trigger = new CronTrigger(cron);// 定時(shí)任務(wù)觸發(fā),可修改定時(shí)任務(wù)的執(zhí)行周期Date nextExecDate = trigger.nextExecutionTime(triggerContext);return nextExecDate;}});}public void stopCron() {if (future != null) {future.cancel(true);//取消任務(wù)調(diào)度}}}
驗(yàn)證可行,作個(gè)記錄,如果有認(rèn)為可以調(diào)整的地方,歡迎討論!
以上這篇淺談springboot項(xiàng)目中定時(shí)任務(wù)如何優(yōu)雅退出就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持好吧啦網(wǎng)。
相關(guān)文章:
1. Python 通過(guò)正則表達(dá)式快速獲取電影的下載地址2. node.js降低版本的方式詳解(解決sass和node.js沖突問(wèn)題)3. python 實(shí)現(xiàn)添加標(biāo)簽&打標(biāo)簽的操作4. 在Archlinux系統(tǒng)中安裝Scim-Python輸入法5. PHP程序員的技術(shù)成長(zhǎng)規(guī)劃6. SpringMVC生成的驗(yàn)證碼圖片不顯示問(wèn)題及解決方法7. 詳解PHP實(shí)現(xiàn)HTTP服務(wù)器過(guò)程8. Ajax實(shí)現(xiàn)登錄案例9. HTML-Canvas的優(yōu)越性能以及實(shí)際應(yīng)用10. vue動(dòng)態(tài)渲染svg、添加點(diǎn)擊事件的實(shí)現(xiàn)
