Spring中任务调度

任务调度

任务调度即在特定的时间点执行指定的操作。任务调度本身设计多线程并发,运行时间规则制定及解析,运行现场保持及恢复,线程池维护等。

quartz是任务调度的成熟解决方案,功能强大使用简单。Spring提供了集成quartz的功能,也为JDK Timer 和 Excutor提供了支持。

在Spring中提供了一系列FactoryBean,可以很轻松的创建任务调度的实例;Spring还提供了几个工具类,可以将某个具体的Bean的方法作为被调度的任务;Spring还提供了支持线程池的执行调度器,它提供了一个抽象层,屏蔽了Java 1.3、JAVA 1.4、JAVA 1.5及Java EE之间的差异。

Quartz

Quartz允许开发人员灵活的定义触发器的调度时间表,并可对触发器和任务进行关联。Quartz提供了调度运行环境的持久化机制,可以保存并恢复调度现场。Quartz提供了监听器,各种插件,线程池等功能。(以下代码基于Quartz 1.8.6)

基础结构

Quartz对任务调度进行了高度的抽象,提出了调度器,任务和触发器三个核心概念,并在org.quartz这个包中通过接口和类对核心概念进行描述。

Job

Job 是一个接口,内部只有一个方法。通过实现该接口定义需要执行的任务。JobExcutionContext提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中。

public interface Job {
    void execute(JobExecutionContext context)
        throws JobExecutionException;
}
  • StatefulJob

    Job的子接口,代表有状态的任务。该接口是个标签接口,让Quartz知道任务的类型,以便采取不同的执行方案。每个无状态的任务有自己的JobDataMap,所有有状态任务共享一个JobDataMap,StatefulJob每次任务执行对JobDataMap的更改会影响后续任务。所以有状态的StatefulJob不能并发执行,后续任务将阻塞等待直到本次任务执行完毕。除非必要,应避免StatefulJob的使用。

JobDetail

Quartz每次执行任务时,都根据接收的Job的实现类Class,通过反射创建一个Job实例,因此需要一个类对Job实现类和其他配置进行描述,如Job名称,描述,关联监听器等(类似于Spring中的BeanDefinition)。

public interface JobDetail extends Serializable, Cloneable {
    public JobKey getKey();
    public String getDescription();
    public Class getJobClass();
    public JobDataMap getJobDataMap();
    ...
}
JobDataMap

JobDataMap中可以包含不限量的(序列化的)数据对象,在job实例执行的时候,可以使用其中的数据;JobDataMap是Java Map接口的一个实现,额外增加了一些便于存取基本类型的数据的方法。
将job加入到scheduler之前,在构建JobDetail时,可以将数据放入JobDataMap。
方式有两种

直接在构建JobDetail时通过JobBuilder的usingJobData方法将数据放入JobDataMap中。方法有两种:直接添加数据或者构造JobDataMap

//构造JobDataMap
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("jobData2", "hello 2");

JobDetail job = JobBuilder.newJob(SimpleJob.class)
    .withIdentity("helloJob","hello")
    .usingJobData("jobData1","hello 1") //添加数据
    .usingJobData(jobDataMap)
    .build();

也可以在job类中,为JobDataMap中存储的数据的key增加set方法,那么Quartz的默认JobFactory实现在job被实例化的时候会自动调用这些set方法,这样就不需要在execute()方法中显式地从map中取数据了。

public class SimpleJob implements Job {

    private String jobData1;
    private String jobData2;

    public void setJobData1(String jobData1) {
        this.jobData1 = jobData1;
    }
    public void setJobData2(String jobData2) {
        this.jobData2 = jobData2;
    }

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
//        System.out.println(context.getJobDetail().getJobDataMap().get("jobData1"));
//        System.out.println(context.getJobDetail().getJobDataMap().get("jobData2"));
        System.out.println(jobData1);
        System.out.println(jobData2);
        System.out.println("hello,quartz!"+ context.getJobDetail().getKey()+":::"+ context.getTrigger().getKey());
    }
}
Trigger

Trigger接口描述触发Job执行的时间触发规则。主要由两个子类接口:仅需要触发一次或以固定时间间隔执行时,适合选择SimpleTriggerCronTrigger适合复杂的调度方案,通过Cron表达式定义时间规则。

Calendar

org.quartz.Calendarjava.util.Calendar不同,它是一些日历特定时间点的集合。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。例如每周一上午9点执行任务,如果遇到法定节假日不执行。针对不同的时间段类型,Quartz提供了不同的实现类,如AnnualCalendar,HolidayCalendar,MonthlyCalendar等。

Scheduler

代表一个Quartz独立运行的容器,Trigger和JobDetail可以注册到Scheduler中,二者在Scheduler中各自拥有组和名称,组成了key。key是Scheduler查找定位容器中某一对象的依据,所以必须唯一(Trigger和JobDetail的key可以相同,因为类型不一样,处在不同的集合中)。

Scheduler定义了多个接口方法,允许外部通过组及名称对容器中的Trigger和JobDetail进行访问控制。

Scheduler可以将Trigger绑定到某一个JobDetail,这样当Trigger触发时,对应Job会被执行,一个Job可以对应多个Trigger,但是一个Trigger只能对应一个Job。

Scheduler实例通过SchedulerFactory创建,Scheduler拥有一个SchedulerContext,SchedulerContext内部维护一个Map,以键值对形式保存上下文信息。job和Trigger都可以访问SchedulerContext内的信息。

Scheduler的生命周期

Scheduler的生命期, 从SchedulerFactory创建它时开始,到Scheduler调用shutdown()方法时结束;Scheduler被创建后,可以增加、删除和列举Job和Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。但是,Scheduler只有在调用start()方法后,才会真正地触发trigger(即执行job)

JobBuilder

用于定义/构建JobDetail实例,用于定义作业的实例。

ThreadPool

Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程来提高运行效率。

SimpleTrigger

Demo
public class SimpleJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("hello,quartz!"+ context.getJobDetail().getKey()+":::"+ context.getTrigger().getKey());
    }
}
public static void main(String[] args) throws Exception{
    //构建SchedulerFactory实例
    SchedulerFactory schedFact = new StdSchedulerFactory();

    //获取Scheduler实例
    Scheduler scheduler = schedFact.getScheduler();

    //构建JobDetail实例
    JobDetail job = JobBuilder.newJob(SimpleJob.class)
        .withIdentity("helloJob","hello")
        .build();

    //构建Trigger实例
    SimpleTrigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("helloTrigger","hello")
        .startNow()         .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
        .build();

    //将JobDetail实例和Trigger实例加入到调度容器
    scheduler.scheduleJob(job,trigger);

    //启动容器
    scheduler.start();

}
Misfire策略
  • MISFIRE_INSTRUCTION_FIRE_NOW

    设置方法:withMisfireHandlingInstructionFireNow

    ——以当前时间为触发频率立即触发执行
    ——执行至EndTIme的剩余周期次数
    ——以调度或恢复调度的时刻为基准的周期频率,EndTIme根据剩余次数和当前时间计算得到
    ——调整后的EndTIme会略大于根据StartTime计算的到的EndTIme值

  • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

    设置方法:withMisfireHandlingInstructionIgnoreMisfires
    含义:
    ——以错过的第一个频率时间立刻开始执行
    ——重做错过的所有频率周期
    ——当下一次触发频率发生时间大于当前时间以后,按照Interval的依次执行剩下的频率
    ——共执行RepeatCount+1次

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_CO

    设置方法:withMisfireHandlingInstructionNowWithExistingCount
    含义:
    ——以当前时间为触发频率立即触发执行
    ——执行至EndTIme的剩余周期次数
    ——以调度或恢复调度的时刻为基准的周期频率,EndTIme根据剩余次数和当前时间计算得到
    ——调整后的EndTIme会略大于根据StartTime计算的到的EndTIme值

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT

    设置方法:withMisfireHandlingInstructionNowWithRemainingCount
    含义:
    ——以当前时间为触发频率立即触发执行
    ——执行至EndTIme的剩余周期次数
    ——以调度或恢复调度的时刻为基准的周期频率,EndTIme根据剩余次数和当前时间计算得到
    ——调整后的EndTIme会略大于根据StartTime计算的到的EndTIme值

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT

    设置方法:withMisfireHandlingInstructionNextWithRemainingCount
    含义:
    ——不触发立即执行
    ——等待下次触发频率周期时刻,执行至EndTIme的剩余周期次数
    ——以StartTime为基准计算周期频率,并得到EndTIme
    ——即使中间出现pause,resume以后保持EndTIme时间不变

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

    设置方法:withMisfireHandlingInstructionNextWithExistingCount
    含义:
    ——此指令导致trigger忘记原始设置的StartTime和repeat-count
    ——触发器的repeat-count将被设置为剩余的次数
    ——这样会导致后面无法获得原始设定的StartTime和repeat-count值

  • 默认策略

    SimpleScheduleBuilder中misfireInstruction的默认值是MISFIRE_INSTRUCTION_SMART_POLICY,这是所有Trigger默认的MisFire策略,这个策略会根据Trigger的状态和类型来自动调节MisFire策略。
    查看源码可以看到,若设置为默认策略,则按照以下规则来选择MisFire策略

    如果重复计数为0,则指令将解释为MISFIRE_INSTRUCTION_FIRE_NOW。
    如果重复计数为REPEAT_INDEFINITELY,则指令将解释为MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT。 警告:如果触发器具有非空的结束时间,则使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT可能会导致触发器在失火时间范围内到达结束时,不会再次触发。
    如果重复计数大于0,则指令将解释为MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT。

CronTrigger

CronTrigger能提供比SimpleTrigger更具有实际意义的调度方案,调度规则基于Cron表达式。

Demo
TriggerBuilder.newTrigger()
    .withSchedule(CronScheduleBuilder.cronSchedule("0 0/30 * * * ? "))
    .forJob("jobA", "groupA")
    .build();
Misfire策略
  • MISFIRE_INSTRUCTION_DO_NOTHING

    设置方法:withMisfireHandlingInstructionDoNothing
    含义:
    ——不触发立即执行
    ——等待下次Cron触发频率到达时刻开始按照Cron频率依次执行

  • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY

    设置方法:withMisfireHandlingInstructionIgnoreMisfires
    含义:
    ——以错过的第一个频率时间立刻开始执行
    ——重做错过的所有频率周期后
    ——当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行

  • MISFIRE_INSTRUCTION_FIRE_ONCE_NOW

    设置方法:withMisfireHandlingInstructionFireAndProceed
    含义:
    ——以当前时间为触发频率立刻触发一次执行
    ——然后按照Cron频率依次执行

  • 默认策略

    在SimpleTrigger中已经提到所有trigger的默认Misfire策略都是MISFIRE_INSTRUCTION_SMART_POLICY,SimpleTrigger会根据tirgger的状态来调整具体的Misfire策略,而CronTrigger的默认Misfire策略会被CronTrigger解释为MISFIRE_INSTRUCTION_FIRE_NOW,具体可以参照CronTrigger实现类的源码

Cron表达式

Cron([krɑn]):代表100万年,是英文中最大的时间单位。

Cron表达式对特殊字符的大小写不敏感。

时间 强制 允许值 允许的特殊字符
Seconds yes 0-59 ,_*/
Minutes yes 0-59 ,_*/
Hours yes 0-23 ,_*/
Day Of month yes 1-31 ,_*?/LW
Month yes 1-12 or JAX-DEC ,_*/
Day of Week yes 1-7 or SUN-SAT ,_*?/L#
Year no empty,1970-2099 ,_*/
  • * :可用在所有字段中,表示对应时间的每一时刻
  • ?:在日期和星期中使用,通常表示无意义的值
  • -:表达一个范围,在小时中10-12表示10点到12点,即10,11,12
  • ,:表示一个列表值
  • /:x/y表示一个等步长序列,x为起始值,y为增量步长值。如在分钟中0/15,表示0,15,30,45秒;也可以使用 */15 ,等同于 0/15
  • L:再日期和星期中使用,代表"Last"。在日期中,代表最后一天,在星期中,代表星期六,等同于7(西方国家认为星期六是一周的最后一天)。例如6L在星期中代表最后一个星期五。
  • W:只能出现在日期中,是对前导日期的修饰,表示离该日期最近的工作日。如15W,如果15日是周天,则匹配16号周一,如果15号是周六,则匹配14号周五。匹配不能跨月,只能指定单一时间。
  • LW:在日期中使用,表示当月最后一天
  • #:只能在星期中使用,表示当月某个工作日。6#7表示第7个星期五(6代表星期五,#7代表第7个),如果当月没有第七个星期5,则不触发。
  • C:只能在日期和星期中使用,代表“Calendar”。意思计划所有关联的日期,如果日期没有被关联,则相当于日期中的所有日期。

Spring中使用Quartz

在Spring中使用Quartz比较简单,导入相关依赖,进行配置即可。(后续补充)


    
         //在这里指定数据源,配置中会失效
    
    
    
    

#==============================================================
#Configure Main Scheduler Properties
#==============================================================
org.quartz.scheduler.instanceName = quartzScheduler
org.quartz.scheduler.instanceId = AUTO

#==============================================================
#Configure JobStore
#==============================================================
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix = QRTZ_
org.quartz.jobStore.isClustered = true
org.quartz.jobStore.clusterCheckinInterval = 20000
#org.quartz.jobStore.dataSource = myDS

#==============================================================
#Configure DataSource
#==============================================================
#org.quartz.dataSource.myDS.driver = com.mysql.jdbc.Driver
#org.quartz.dataSource.myDS.URL =jdbc:mysql://localhost:3306/quartz_db?useUnicode=true&characterEncoding=UTF-8
#org.quartz.dataSource.myDS.user = root
#org.quartz.dataSource.myDS.password = 123456
#org.quartz.dataSource.myDS.maxConnections = 30

#==============================================================
#Configure ThreadPool
#==============================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount = 100
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true

你可能感兴趣的:(Spring中任务调度)