Quartz定时任务管理方式(动态添加、停止、恢复、删除定时任务)
作者:古甲哈醒
Quartz定时任务框架经常用于系统后台业务异步处理。平常我们使用时,主要是通过手工编写配置代码文件方式添加修改定时任务,然后重启系统。
有时候我们需要根据业务运营需要,动态添加修改定时任务,比如添加新的定时任务、修改任务执行时间、暂停定时任务、删除定时任务等,并且监控定时任务状态,而又不想重启系统,这时就需要系统具备动态管理定时任务的功能。
Quartz提供了一系列组件,支持动态管理定时任务的功能。
Quartz定时任务主要由Scheduler、JobDetail、CronTrigger、Cron组成,实现动态管理定时任务,主要就是通过管理上述对象来实现的。
1、数据库设计
主要将我们平时配置的任务计划放入数据库中保存。在启动任务是,从数据库中查找任务计划信息,并动态配置进去即可。
DROP TABLE IF EXISTS `cc_task_info`; CREATE TABLE `cc_task_info` ( `TID` int(11) NOT NULL AUTO_INCREMENT, `TASK_ANME` varchar(50) NOT NULL, `TASK_CODE` varchar(50) NOT NULL, `JOB_CLASS` varchar(200) NOT NULL, `JOB_GROUP` varchar(50) NOT NULL, `CRON` varchar(50) NOT NULL, `DEL_STATUS` varchar(2) DEFAULT '1' NULL, `CRT_TIME` datetime DEFAULT NULL, PRIMARY KEY (`TID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务管理表'; DROP TABLE IF EXISTS `cc_task_record`; CREATE TABLE `cc_task_record` ( `RID` int(11) NOT NULL AUTO_INCREMENT, `TASK_CODE` varchar(50) NOT NULL, `RUN_TIME` datetime NOT NULL, `RUN_CODE` char(1) NOT NULL, `RUN_MSG` varchar(100) NULL, PRIMARY KEY (`RID`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务运行记录表'; DROP TABLE IF EXISTS `cc_task_status`; CREATE TABLE `cc_task_status` ( `TASK_CODE` varchar(50) NOT NULL, `TASK_STATUS` varchar(10) NOT NULL, `LST_SUCC_TIME` datetime NOT NULL, `LST_TIME` datetime NOT NULL, PRIMARY KEY (`TASK_CODE`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='定时任务运行状态表';
2、定时任务管理
定时任务管理主要是通过Scheduler的方法来实现。Scheduler提供了一系列方法来管理定时任务的执行状态。
主要包括:
- scheduleJob():添加定时任务
- rescheduleJob():修改定时任务
- pauseJob():暂停定时任务执行
- resumeJob():恢复定时任务执行
- deleteJob():删除定时任务执行
针对上述方法,我们只需要传入对应参数即可。
这里我建了一个QuartzService来管理定时任务,供业务层调用。
详细代码如下:
/** * 定时任务管理服务 */ @Service public class QuartzService { public static String SCHEDULER_OPR_START = "start"; public static String SCHEDULER_OPR_PAUSE = "pause"; public static String SCHEDULER_OPR_RESUME = "resume"; public static String SCHEDULER_OPR_REMOVE = "remove"; @Autowired private Scheduler scheduler; /** * 启动任务 */ public void startJob(String taskCode, String taskAnme, String cron, String jobGroup, String className) throws Exception{ Class<Job> jobClass = null; try { jobClass = (Class<Job>) Class.forName(className);//获取任务执行类 } catch (ClassNotFoundException e) { throw new Exception("任务类不存在"); } //创建job,指定job名称和分组 JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(taskCode, jobGroup).build(); //创建表达式工作计划 CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(cron); //创建触发器 CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(taskCode, jobGroup) .withSchedule(cronScheduleBuilder).build(); scheduler.scheduleJob(jobDetail, cronTrigger); } /** * 修改定时任务执行时间 * @param taskCode * @param jobGroup * @param cron 新的时间 * @throws Exception */ public void modifyJob(String taskCode, String jobGroup, String cron) throws Exception{ TriggerKey triggerKey = new TriggerKey(taskCode, jobGroup); CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey); String oldCron = trigger.getCronExpression(); if(!oldCron.equals(cron)){ CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(taskCode, jobGroup) .withSchedule(CronScheduleBuilder.cronSchedule(cron)).build(); Date date = scheduler.rescheduleJob(triggerKey, cronTrigger); if(date == null){ throw new Exception("修改定时任务执行时间报错"); } } } /** * 暂停某个定时任务(任务恢复后,暂停时间段内未执行的任务会继续执行,如暂停时间段内有2次,则会执行2次) * @param taskCode * @param jobGroup * @throws Exception */ public void pauseJob(String taskCode, String jobGroup) throws Exception{ JobKey jobKey = new JobKey(taskCode, jobGroup); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if(jobDetail == null){ return; } scheduler.pauseJob(jobKey); } /** * 恢复某个定时任务 * @param taskCode * @param jobGroup * @throws Exception */ public void resumeJob(String taskCode, String jobGroup) throws Exception{ JobKey jobKey = new JobKey(taskCode, jobGroup); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if(jobDetail == null){ return; } scheduler.resumeJob(jobKey); } /** * 删除某个定时任务 * @param taskCode * @param jobGroup * @throws Exception */ public void deleteJob(String taskCode, String jobGroup) throws Exception{ JobKey jobKey = new JobKey(taskCode, jobGroup); JobDetail jobDetail = scheduler.getJobDetail(jobKey); if(jobDetail == null){ return; } scheduler.deleteJob(jobKey); } }
3、编写任务类JOB
任务类JOB就是定时任务具体要处理的系统业务逻辑,需要实现Job接口。
在任务启动时,通过jobClass传入JobDetail。
public class DemoJob implements Job { @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { String taskCode = jobExecutionContext.getJobDetail().getKey().getName(); System.out.println("执行定时任务:" + taskCode); } }
4、配置Scheduler
在Configuration中配置Scheduler实例,并启动。
@Configuration public class QuartzConfig { @Bean public Scheduler scheduler(){ Scheduler scheduler = null; SchedulerFactory factory = new StdSchedulerFactory(); try { scheduler = factory.getScheduler(); } catch (SchedulerException e) { e.printStackTrace(); } if(scheduler != null){ try { //启动定时任务 scheduler.start(); } catch (SchedulerException e) { e.printStackTrace(); } } return scheduler; } }
5、编写API接口
通过Controller提供API接口,这里我的TaskService调用了QartzService的对应接口,并做了一个写数据库读写操作,主要记录定时任务状态、执行记录信息的等。
@RestController @RequestMapping("/api/task") public class TaskController { @Autowired private TaskService service; @RequestMapping("/start") public Object start(int id){ try { service.startJob(id); return RtnData.ok(); } catch (Exception e) { return RtnData.fail(e.getMessage()); } } @RequestMapping("/pause") public Object pause(int id){ try { service.pauseJob(id); return RtnData.ok(); } catch (Exception e) { return RtnData.fail(e.getMessage()); } } @RequestMapping("/resume") public Object resume(int id){ try { service.resumeJob(id); return RtnData.ok(); } catch (Exception e) { return RtnData.fail(e.getMessage()); } } @RequestMapping("/remove") public Object remove(int id){ try { service.deleteJob(id); return RtnData.ok(); } catch (Exception e) { return RtnData.fail(e.getMessage()); } } }
6、接口测试
先在数据库中将DemoJob添加到任务管理表中,然后使用postman调用api进行任务启动、修改、暂停、恢复、删除等操作,观察系统后台日志打印情况查看效果。
数据库初始化如下:
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。