Quartz 任务调度

    Quartz是一个完全由java编写的开源作业调度框架。为在 Java 应用程序中进行作业调度提供了简单却强大的机制。多用在轮询、定时任务处理等。相对于JDK 自带的Timer 有着更强大的功能。
    使用Quartz需要在quartz.jar官网下载相应的jar包(访问可能比较慢) 或者 pom文件导入

org.quartz-scheduler

quartz

2.2.1
一、核心要素

JobDetail
    表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略,也就是业务逻辑。
Trigger
    代表一个调度参数的配置,什么时候去调,先当于定时器。
Scheduler
    代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了。

二、配置步骤

1. 实现 Job 接口,编写业务逻辑

public class xxxJob implements Job {
   @Override
   public void execute(JobExecutionContext context) throws JobExecutionException {
      // 业务逻辑...
   }
}

2. JobDetail 配置 Job

JobDetail jobDetail = JobBuilder.newJob(xxxJob.class)
      .withIdentity("Job", "Group")   // 定义任务名及分组
      .build();

除上述外还可定义任务描述、配置相关业务数据等功能,相见 quartz 2.2.2 API

3. 配置 Trigger 定时器

Trigger trigger = TriggerBuilder.newTrigger()
      .withIdentity("Trigger", "Group")
      .startAt(DateBuilder.nextGivenMinuteDate(null, 5))
      .withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 * * * ?"))
      .build();

定时器可分为 SimpleTrigger 和 CronTrigger 两类,前者相当于 Timer 中的定时配置,用于简单的定时规则,而后者则用于处理复杂的定时逻辑,可用表达式来进行配置。

SimpleTrigger

SimpleScheduleBuilder
    .repeatMinutelyForever(10)
    .withMisfireHandlingInstructionNextWithExistingCount()
    .build();

上面的 repeatMinutelyForever(10) 根据字面意思就可得 定时任务为“每10分钟执行一次”,而且是不会停止一直执行下去。除此外还可以按小时的单位进行配置,还可定义任务执行的总次数。

在任务执行中还可能存在这样一个问题:到了该触发执行时上一个执行任务还未完成,且线程池中没有空闲线程可以使用,我们将这样的任务就定义为错失触发(misfire)。产生这种问题可能是由于服务器重启或内存空间不足等原因导致,因此在配置中也要对错失触发有相应的处理。

比如:13:07:24开始执行,重复执行5次,开始执行时,quartz已经计算好每次调度的时间刻,分别如下:
03:33:36,03:33:39,03:33:42,03:33:45,03:33:48,03:33:51

如果第一次执行时间为11s,到03:33:47结束,03:33:47减去03:33:39的时间间隔是8s,如果misfireThreshold设置的时间小于等于8s间隔,则认为是misfire了,如果大于8s间隔,则认为没有misfire。

SimpleTrigger 错失、补偿执行:

① withMisfireHandlingInstructionFireNow();                           // 以当前时间为触发频率立即触发执行(若trigger重复执行,执行同⑥)
② withMisfireHandlingInstructionIgnoreMisfires();                    // 以错过的第一个频率时间立刻开始执行
③ withMisfireHandlingInstructionNextWithExistingCount();             // 不触发立即执行
④ withMisfireHandlingInstructionNextWithRemainingCount();            // 不触发立即执行
⑤ withMisfireHandlingInstructionNowWithExistingCount(); (default)   // 以当前时间为触发频率立即触发执行
⑥ withMisfireHandlingInstructionNowWithRemainingCount();             // 以当前时间为触发频率立即触发执行

CronTrigger

CronScheduleBuilder
    .cronSchedule("0 0/1 * * * ?")
    .withMisfireHandlingInstructionDoNothing()
    .build();

上面可以看出 CronTrigger 的执行时间是通过 表达式 配置的。分别对应 “秒、分、时、月份中的日期、月、[星期中的日期]、年份”。上面的配置就是“定时任务每分钟执行一次”。

Cron表达式:

时间 范围 可用字符
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * ? / L W C
1-12 or JAN-DEC , - * /
1-7 or SUN-SAT , - * ? / L C #
年(可选) empty, 1970-2099 , - * /

*:表示匹配该域的任意值。假如在Minutes域使用*, 即表示每分钟都会触发事件。

-:表示范围。例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次。

,:表示列出枚举值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

?: 只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和 DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。

/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次。

L: 表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

W: 表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一 到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份。

LW: 这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

#: 用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

C: 指和calendar联系后计算过的值。例:在day-of-month 字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天。

CronTrigger错失、补偿执行:

① withMisfireHandlingInstructionDoNothing()                      // 不触发立即执行
② withMisfireHandlingInstructionIgnoreMisfires();                // 以错过的第一个频率时间立刻开始执行
③ withMisfireHandlingInstructionFireAndProceed();  (default)     // 以当前时间为触发频率立刻触发一次执行

4. Scheduler 将 JobDetail、Trigger结合

// 创建工厂
SchedulerFactory schedulerFactory = new StdSchedulerFactory();  // method 1
// schedulerFactory = DirectSchedulerFactory.getInstance();     // method 2
// 从工厂获取调度器
Scheduler scheduler = schedulerFactory.getScheduler();
// 注册任务、定时器
scheduler.scheduleJob(jobDetail, trigger);
// 启动
scheduler.start();

经过上述配置之后就可以执行定时任务了。

三、Spring 的封装

    由于Quartz被公认为是比较好用的定时器设置工具,因此在spring框架中也有将其进行进一步的封装。

1. 实现 QuartzJobBean 接口

@DisallowConcurrentExecution
public class xxxJob extends QuartzJobBean {
    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        // 业务逻辑...
    }
}

2. JobDetailFactoryBean 配置

JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
jobDetailFactoryBean.setJobClass(xxxJob.class);
jobDetailFactoryBean.setJobDataAsMap(map);
jobDetailFactoryBean.setDurability(true);

3. xxxTriggerFactoryBean 配置

SimpleTriggerFactoryBean simpleTriggerFactoryBean = new SimpleTriggerFactoryBean();
simpleTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
simpleTriggerFactoryBean.setRepeatInterval(60 * 1000);

CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean();
cronTriggerFactoryBean.setJobDetail(jobDetailFactoryBean.getObject());
cronTriggerFactoryBean.setCronExpression("10 0/5 * * * ?");

4. SchedulerFactoryBean 结合

SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setTriggers(
    simpleTriggerFactoryBean.getObject(), 
    cronTriggerFactoryBean.getObject()
);

由于 spring 只是将其进行了封装整合,只是使用上会方便很多,但具体实现方法还是一样的,可参考上述的配置步骤。

你可能感兴趣的:(Java)