利用spring+quartz-scheduler+MySql实现定时任务动态调整

一、需求

       由于可能要动态调整某些定时任务的执行,然而目前WEB方面的现状是所有定时任务是利用spring @Scheduled注解方式实现,不方便动态在调整定时任务的执行时间

 

 

二、分析

        在Spring中使用Quartz有两种方式实现:第一种是任务类继承QuartzJobBean,第二种则是在配置文件里定义任务类和要执行的方法,类和方法可以是普通类。因此第二种方式远比第一种方式来的灵活。所以这个分析设计也是基于spring配置的方式

       Quartz中是以分组名+任务名作为任务的唯一key,为与quartz中的实现方式一致,本设计也采用这样的方式


注:spring @Scheduled注解方式,定时任务的执行Job在内存,不方便动态调整,并且不支持比如最后一个工作0 0 17 ? * MON-FRI  0 0 15 LW * ?等诸如此类的cron表达式,功能相对quartz比较弱些

三、实现

3.1 数据表的设计

直接上表吧

CREATE   TABLE   `schedule_job` (
   `job_id`  varchar (45)  NOT   NULL   DEFAULT   ''   COMMENT  '任务id,用于区分业务' ,
   `job_name`  varchar (64)  NOT   NULL   DEFAULT   ''   COMMENT  '任务名称' ,
   `job_group`  varchar (64)  NOT   NULL   DEFAULT   'DEFAULT'   COMMENT  '任务分组' ,
   `job_status`  varchar (32)  DEFAULT   '1'   COMMENT  '任务状态 0禁用 1启用 2删除' ,
   `cron_expression`  varchar (64)  DEFAULT   NULL   COMMENT  '任务运行时间表达式' ,
   `job_desc`  varchar (256)  DEFAULT   NULL   COMMENT  '任务描述' ,
   `create_time`  int (11)  DEFAULT   NULL ,
   `update_time`  int (11)  DEFAULT   NULL ,
   `operator`  varchar (64)  DEFAULT   NULL ,
   PRIMARY   KEY   (`job_group`,`job_name`)
) ENGINE=InnoDB  DEFAULT   CHARSET=utf8 COMMENT= '动态任务调度控制' ;
 
 


3.2 Quartz 的trigger各种状态说明

  1. None:Trigger已经完成,且不会在执行,或者找不到该触发器,或者Trigger已经被删除
  2. NORMAL:正常状态
  3. PAUSED:暂停状态
  4. COMPLETE:触发器完成,但是任务可能还正在执行中
  5. BLOCKED:线程阻塞状态
  6. ERROR:出现错误

3.3 动态暂停 恢复 修改和删除任务

      Quartz的任务信息默认是保存的内存里的,没有保存到数据库,使用的是RAMJobStore,当然如果你有需要,可以实现成JDBCJobStore,那样任务信息将会更全面

1、获取计划中的任务

计划中的任务通过配置文件配置,详细配置如下

< bean   id = "XXXXTask"   class = "cn.XXXX.Task" />
< bean   id = "xxxxJobDetail"   class = "org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean" >
     < property   name = "group"   value = "xxx" />
     < property   name = "name"   value = "xxxx" />
     < property   name = "concurrent"   value = "false" />
     < property   name = "targetObject" >
         < ref   bean = "XXXXTask" />
     property >
     < property   name = "targetMethod" >
         < value >xxxxx value >
     property >
bean >
< bean   id = "xxxxxTrigger"   class = "org.springframework.scheduling.quartz.CronTriggerFactoryBean" >
     < property   name = "name"   value = "xxxx" />
     < property   name = "group"   value = "xxxx" />
     < property   name = "jobDetail" >
         < ref   bean = "xxxxJobDetail" />
     property >
     < property   name = "cronExpression" >
        
         < value >${task.cron.xxxx} value >
     property >
bean >
 
< bean   id = "scheduler"   class = "org.springframework.scheduling.quartz.SchedulerFactoryBean" >
     < property   name = "triggers" >
         < list >
            
             < ref   bean = "xxxxxTrigger" />
         list >
     property >
bean >
/**
  * 计划中的任务
  *
  * @param
  * @return
  * @author hongshu
  * Created By 2017/9/21 13:54
  */
public   List plannedJob() {
     try   {
         Scheduler scheduler = schedulerFactoryBean.getScheduler();
         GroupMatcher matcher = GroupMatcher.anyJobGroup();
         Set jobKeys = scheduler.getJobKeys(matcher);
         List jobList =  new   ArrayList();
         for   (JobKey jobKey : jobKeys) {
             List  extends   Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
             for   (Trigger trigger : triggers) {
                 ScheduleJob job =  new   ScheduleJob();
                 job.setJobName(jobKey.getName());
                 job.setJobGroup(jobKey.getGroup());
                 job.setJobDesc( "触发器:"   + trigger.getKey());
                 Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                 job.setJobStatus(triggerState.name());
                 if   (trigger  instanceof   CronTrigger) {
                     CronTrigger cronTrigger = (CronTrigger) trigger;
                     String cronExpression = cronTrigger.getCronExpression();
                     job.setCronExpression(cronExpression);
                 }
                 jobList.add(job);
             }
         }
         return   jobList;
     }  catch   (Exception e) {
         logger.error( "plannedJob "   + e.getMessage(), e);
     }
     return   null ;
}

 

2、暂停与恢复任务

/**
  * 恢复任务
  *
  * @param
  * @return
  * @author hongshu
  * Created By 2017/9/21 13:54
  */
public   void   resumeJob(String jobName, String jobGroup) {
     try   {
         JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
         Scheduler scheduler = schedulerFactoryBean.getScheduler();
         scheduler.resumeJob(jobKey);
         logger.error( "恢复任务"   + jobGroup +  "."   + jobName +  "成功" );
     }  catch   (SchedulerException e) {
         logger.error( "恢复任务"   + jobGroup +  "."   + jobName +  "异常:"   + e.getMessage(), e);
     }
 
}
 
/**
  * 暂停任务
  *
  * @param
  * @return
  * @author hongshu
  * Created By 2017/9/21 13:54
  */
public   void   pauseJob(String jobName, String jobGroup) {
     try   {
 
         Scheduler scheduler = schedulerFactoryBean.getScheduler();
         JobKey jobKey = JobKey.jobKey(jobName, jobGroup);
         scheduler.pauseJob(jobKey);
         logger.error( "暂停任务"   + jobGroup +  "."   + jobName +  "成功" );
     }  catch   (SchedulerException e) {
         logger.error( "暂停任务"   + jobGroup +  "."   + jobName +  "异常:"   + e.getMessage(), e);
     }
}

 

3、更新cronExpression

/**
      * 更新定时任务cornExpression
      *
      * @param
      * @return
      * @author hongshu
      * Created By 2017/9/20 15:52
      */
     public   void   loadCronExpression() {
         Scheduler scheduler = schedulerFactoryBean.getScheduler();
 
//        List runningJob = runningJob();
         /*查询计划中的任务*/
         List plannedJob = plannedJob();
 
         for   (ScheduleJob job : plannedJob) {
             try   {
                 TriggerKey triggerKey = TriggerKey.triggerKey(job.getJobName(), job.getJobGroup());
 
                 //获取trigger,即在spring配置文件中定义的 bean id="myTrigger"
                 CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
                 //不存在,创建一个
                 if   ( null   == trigger) {
                     JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory. class )
                             .withIdentity(job.getJobName(), job.getJobGroup()).build();
                     jobDetail.getJobDataMap().put( "scheduleJob" , job);
 
                     //表达式调度构建器
                     CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(job
                             .getCronExpression());
 
                     //按新的cronExpression表达式构建一个新的trigger
                     trigger = TriggerBuilder.newTrigger().withIdentity(job.getJobName(), job.getJobGroup()).withSchedule(scheduleBuilder).startNow().build();
 
                     scheduler.scheduleJob(jobDetail, trigger);
                 }  else   {
 
                     ScheduleJob dbJob = scheduleJobService.queryScheduleJobByPriKey(job.getJobName(), job.getJobGroup());
                     if   (dbJob !=  null   && StringUtils.isNotBlank(dbJob.getCronExpression())) {
 
                         /*暂停任务*/
                         pauseJob(job.getJobName(), job.getJobGroup());
 
                         Date now =  new   Date();
                         Date rawFireTimeAfter = trigger.getFireTimeAfter(now);
                         String rawCron = trigger.getCronExpression();
 
                         // Trigger已存在,那么更新相应的定时设置
                         //表达式调度构建器
                         CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(dbJob.getCronExpression());
 
                         //按新的cronExpression表达式重新构建trigger
                         trigger = trigger.getTriggerBuilder().withIdentity(triggerKey)
                                 .withSchedule(scheduleBuilder).startNow().build();
 
                         Date fireTimeAfter = trigger.getFireTimeAfter(now);
                         String newCron = trigger.getCronExpression();
 
                         //按新的trigger重新设置job执行
                         scheduler.rescheduleJob(triggerKey, trigger);
 
                         /*恢复任务*/
                         resumeJob(job.getJobName(), job.getJobGroup());
                         logger.info( "定时任务更新,cron调整前{},调整后{}" , rawCron, newCron);
                         logger.info( "定时任务更新,下次开火时间调整前{},调整后{}" , DateFormatUtils.format(rawFireTimeAfter,  "yyyy-MM-dd HH:mm:ss" ), DateFormatUtils.format(fireTimeAfter,  "yyyy-MM-dd HH:mm:ss" ));
                     }
 
                 }
             }  catch   (Exception e) {
                 logger.error( "动态任务调整"   + e.getMessage(), e);
             }
         }
     }

4、动态添加新的任务

由于定时任务会处理相应的业务逻辑,动态添加新的定时任务不做过多介绍,在更新cronExpression的实现中,当没有发现trriger时,会自动创建新的定时任务,然后有一个

QuartzJobFactory类会执行新添加的任务,只时做 了打印处理
/**
  * 动态定时任务运行工厂类
  *
  * @author hongshu
  * Created By 2017/9/20 10:39.
  */
@Component
public   class   QuartzJobFactory  implements   Job{
     private   static   final   Logger logger = Logger.getLogger(QuartzJobFactory. class );
     @Override
     public   void   execute(JobExecutionContext context)  throws   JobExecutionException {
         logger.info( "任务成功运行" );
         ScheduleJob scheduleJob = (ScheduleJob) context.getMergedJobDataMap().get( "scheduleJob" );
         logger.info( "任务名称 = ["   + scheduleJob.getJobName() +  "]" );
     }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

注意:JobDetail与Trigger中的name、group属性要保持一致,group默认为default

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

此处的dbJob是存储于Mysql,根据jobName+jobGroup动态更新定时任务的执行时间

 

 

 

 

 

 

 

 

四、使用

当系统接收到更新Quzrtz的指令后,系统去更新数据库,数据更新成功后,调用loadCronExpression()去更新Quartz的定时任务执行计划。

你可能感兴趣的:(spring,java)