在一個springboot項目中需要跑定時任務處理批數據時,突然有個Kill命令或者一個Ctrl+C的命令,此時我們需要當批數據處理完畢后才允許定時任務關閉,也就是當定時任務結束時才允許Kill命令生效。
啟動類
啟動類上我們獲取到相應的上下文,捕捉相應命令。在這里插入代碼片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
@SpringBootApplication /**指定mapper對應包的路徑*/ @MapperScan ( "com.youlanw.kz.dao" ) /**開啟計劃任務*/ @EnableScheduling /**開啟異常重試機制*/ @EnableRetry public class YlkzTaskApplication { public static ConfigurableApplicationContext context; public static void main(String[] args) { context = SpringApplication.run(YlkzTaskApplication. class , args); /** * 捕捉命令實現優雅退出 */ MySignalHandler.install( "TERM" ); //捕捉kill命令 MySignalHandler.install( "INT" ); //捕捉ctrl+c命令 } } |
優雅退出配置類
通過install方法捕捉到相應的命令,
通過signalAction方法進行總開發的控制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
import org.slf4j.LoggerFactory; import sun.misc.Signal; import sun.misc.SignalHandler; /** * @description: 定時任務控制類(實現優雅退出) * @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; /** * 定時任務總開關-狀態:true:打開 false:關閉 */ 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 { //關閉總開關 this .base_flag = false ; logger.info( "\n執行優雅退出操作\n等待運行中任務執行完畢…………" ); Thread.sleep( 3000 ); StringBuffer stringBuffer = new StringBuffer( "a" ); //此處為相關的業務代碼,只要還有一個定時任務在執行,那么就等待線程任務執行完畢。 while (BaseApplyTask.apply_flag || BaseResumeTask.resume_flag || CorpDemandTask.demand_flag || RecommendResumeTask.resume_flag || BaseCodeTask.code_flag || RecommendoneTask.resume_flag ||ResumeByZcbTask.zpbresume_flag) { //等待線程任務執行完畢 stringBuffer.append( "" ); } //獲取到的上下文對象關閉相應的程序。 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(); } } } |
舉例說明
我們在定時任務中添加一個總開關,當總開關是關著時是不允許定時任務執行的,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
@Component public class BaseCodeTask { private final static Logger logger = LoggerFactory.getLogger(BaseCodeTask. class ); @Autowired private ResumeService resumeService; public static boolean code_flag = true ; //簡歷任務執行狀態 true:執行中 false:執行完畢 private static final Integer LIMIT = 500 ; private final static long time = 60 * 1000 ; //一分鐘 /** * @param * @description: 同步簡歷信息(定時任務) * 任務執行間隔時間:6秒 * 待同步數據為空,則5分鐘后執行下一次 * @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 { //總開關狀態:true:打開 false:關閉 if (!MySignalHandler.base_flag) return ; this .code_flag = true ; logger.info( "\n======定時任務:初始化基本數據======\n開始執行\n" ); //以下是業務代碼。相關的定時任務批處理 resumeService.initializationMap(); resumeService.setCodeDictionary(); resumeService.setCityInfo(); resumeService.setCodePostInfo(); logger.info( "\n======定時任務:初始化基本數據======\n結束\n" ); } catch (Exception e) { e.printStackTrace(); } finally { this .code_flag = false ; } } } |
定時任務優雅退出的使用可以有效的防止批處理任務的中斷,小伙伴們可以嘗試添加哦。。。。
補充知識:springboot自帶定時器實現定時任務的開啟關閉以及動態修改定時規則
最近項目中遇到了需要自動定時導出的需求,用戶可以從頁面修改導出的時間規則,可以啟用和停用定時任務。
經過了解,項目中目前實現定時任務,一般有三種選擇,一是用Java自帶的timer類。稍微看了一下,可以實現大部分的指定頻率的任務的調度(timer.schedule()),也可以實現關閉和開啟(timer.cancle)。但是用其來實現某天的某個時間或者某月的某一天調度任務有點不方便。
二是采用Quartz 調度器實現。這是一個功能很強大的開源的專門用于定時任務調度的框架,也很好的和springboot整合,缺點:配置復雜,需要花費一定的時間去了解和研究。(本人懶,因此沒有選擇這個,但是這個功能地區強大,有時間研究)
三是spring3.0以后自帶的scheduletask任務調度,可以實現quartz的大部分功能,不需要額外引用jar,也不需要另外配置。而且支持注解和配置文件兩種。
因此最后選擇直接用spring自帶的task 實現。
基本用法很簡單,通過在方法上加注解@schedule(也可以通過xml文件配置的方式),注解里有 cron ,fixedDelay ,fixedRate ,initialDelay 等等參數,可以完成指定時間,平率執行此方法。這里不詳細介紹。
直接介紹,通過頁面動態修改cron參數,修改定時規則的思路。
1 實現接口SchedulingConfigurer,這個接口只有一個方法,配置定時任務。重寫此方法,添加新的任務實現runable和新的觸發 實現trigger 。
2 在新的觸發里,把修改的cron寫入新的觸發
3 寫UI 方法,接收前端修改的定時參數。
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
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; @Component public class DynamicScheduledTask implements SchedulingConfigurer { @Autowired private 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; } @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // TODO Auto-generated method stub taskRegistrar.addTriggerTask( new Runnable() { @Override public void run() { // TODO Auto-generated method stub try { scheduleExport.scheduleTaskExport(); //異步定時生成文件 System.out.println( "Msg:定時生成文件成功" ); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); System.out.println( "Error:定時生成文件錯誤" ); } } }, new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { // TODO Auto-generated method stub if ( "" .equals(cron)|| cron == null ) return null ; CronTrigger trigger = new CronTrigger(cron); // 定時任務觸發,可修改定時任務的執行周期 Date nextExecDate = trigger.nextExecutionTime(triggerContext); return nextExecDate; } }); System.out.println( "can?" ); } } |
這個方法可以實現 根據頁面設置動態修改定時器的cron參數,不用重啟服務。但是運行之后發現了一個缺陷,即必須在修改完之后,只有再一次到達定時任務的時間,才會調用新的觸發時間, 這就導致,頁面設置的時間并不能即時生效,這在項目中是不符合用戶的要求,于是為了解決這個bug,換了另外一種解決方法。
思路:(了解ThreadPoolTaskScheduler這個類,TaskScheduler接口的默認實現類,多線程定時任務執行。可以設置執行線程池數(默認一個線程))
1、ThreadPoolTaskScheduler 實現TaskScheduler,可以通過方法 schedule(java.lang.Runnable task, Trigger trigger),添加定時任務和觸發器。返回java.util.concurrent.ScheduledFuture<?>,future可以控制任務的開關等。
2、前端修改定時參數,在set方法中修改ThreadPoolTaskScheduler 的觸發器。
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
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; @Component public class DynamicScheduleTaskSecond { @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; @Autowired private 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() { @Override public void run() { // TODO Auto-generated method stub try { scheduleExport.scheduleTaskExport(); // 異步定時生成文件 System.out.println( "Msg:定時生成文件成功" ); } catch (Exception e) { // TODO: handle exception e.printStackTrace(); System.out.println( "Error:定時生成文件錯誤" ); } } }, new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { // TODO Auto-generated method stub if ( "" .equals(cron) || cron == null ) return null ; CronTrigger trigger = new CronTrigger(cron); // 定時任務觸發,可修改定時任務的執行周期 Date nextExecDate = trigger.nextExecutionTime(triggerContext); return nextExecDate; } }); } public void stopCron() { if (future != null ) { future.cancel( true ); //取消任務調度 } } } |
驗證可行,作個記錄,如果有認為可以調整的地方,歡迎討論!
以上這篇淺談springboot項目中定時任務如何優雅退出就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持服務器之家。
原文鏈接:https://blog.csdn.net/weixin_42395975/article/details/95946020