Quartz

目录

 

1.什么是定时任务?为什么需要定时任务?

2.关键组件关系图

3.关键组件介绍

3.1.Trigger的公共属性

3.2.优先级(priority)

3.3.错过触发(misfire Instructions)(重点)

3.3.1.触发器超时 

3.3.2.misfireThreshold 

3.3.3.调度器怎么处理超时

3.4.日历示例(calendar)

4.Job Stores 持久化

4.1.存储形式

5.监听器

5.1.TriggerListeners和JobListeners

5.2.SchedulerListeners


1.什么是定时任务?为什么需要定时任务?

2.关键组件关系图

3.关键组件介绍

最常用的两种trigger分别是:SimpleTrigger和:CronTrigger


3.1.Trigger的公共属性

trigger的公共属性有:

jobKey属性:当trigger触发时被执行的job的身份;
startTime属性:设置trigger第一次触发的时间;该属性的值是java.util.Date类型,表示某个指定的时间点;有些类型的trigger,会在设置的startTime时立即触发,有些类型的trigger,表示其触发是在startTime之后开始生效。比如,现在是1月份,你设置了一个trigger–“在每个月的第5天执行”,然后你将startTime属性设置为4月1号,则该trigger第一次触发会是在几个月以后了(即4月5号)。
endTime属性:表示trigger失效的时间点。比如,”每月第5天执行”的trigger,如果其endTime是7月1号,则其最后一次执行时间是6月5号。

3.2.优先级(priority)


如果你的trigger很多(或者Quartz线程池的工作线程太少),Quartz可能没有足够的资源同时触发所有的trigger;这种情况下,你可能希望控制哪些trigger优先使用Quartz的工作线程,要达到该目的,可以在trigger上设置priority属性。比如,你有N个trigger需要同时触发,但只有Z个工作线程,优先级最高的Z个trigger会被首先触发。如果没有为trigger设置优先级,trigger使用默认优先级,值为5;priority属性的值可以是任意整数,正数、负数都可以。

注意:只有同时触发的trigger之间才会比较优先级。10:59触发的trigger总是在11:00触发的trigger之前执行。

注意:如果trigger是可恢复的,在恢复后再调度时,优先级与原trigger是一样的。


3.3.错过触发(misfire Instructions)(重点)


trigger还有一个重要的属性misfire;如果scheduler关闭了,或者Quartz线程池中没有可用的线程来执行job,此时持久性的trigger就会错过(miss)其触发时间,即错过触发(misfire)。不同类型的trigger,有不同的misfire机制。它们默认都使用“智能机制(smart policy)”,即根据trigger的类型和配置动态调整行为。当scheduler启动的时候,查询所有错过触发(misfire)的持久性trigger。然后根据它们各自的misfire机制更新trigger的信息。当你在项目中使用Quartz时,你应该对各种类型的trigger的misfire机制都比较熟悉。


3.3.1.触发器超时 

举个例子说明这个概念。比如调度引擎中有5个线程,然后在某天的下午2点 有6个任务需要执行,那么由于调度引擎中只有5个线程,所以在2点的时候会有5个任务会按照之前设定的时间正常执行,有1个任务因为没有线程资源而被延迟执行,这个就叫触发器超时。下面这些情况会造成触发器超时:

1)系统因为某些原因被重启。在系统关闭到重新启动之间的一段时间里,可能有些任务会 被 misfire;

2)Trigger 被暂停(suspend)的一段时间里,有些任务可能会被 misfire;

3)线程池中所有线程都被占用,导致任务无法被触发执行,造成 misfire;

4)有状态任务在下次触发时间到达时,上次执行还没有结束;

 

3.3.2.misfireThreshold 

 

misfireThreshold 即触发器超时的临界值,它可以在quartz.properties文件中配置。misfireThreshold是用来设置调度引擎对触发器超时的忍耐时间。假设misfireThreshold设置为6000(单位毫秒),那么它的意思说当一个触发器超时时间大于misfireThreshold时,调度器引擎就认为这个触发器真正的超时(即Misfires)。换言之,如果一个触发器超时时间小于设定的misfireThreshold, 那么调度引擎则不认为触发器超时。也就是说这个job并没发生misfire。

quartz.properties中的配置

#判定job为misfire的阈值,这里设置为4S
org.quartz.jobStore.misfireThreshold = 4000
那么,调度器对于触发器超时但是超时时间小于misfireThreshold 或者 触发器已经misfire 的两种情况是怎么处理的呢?

3.3.3.调度器怎么处理超时

3.3.3.1.timeout < misfireThreshold
为了制造超时的现象,实验时把线程池的大小设定为1,misfireThreshold设定为5S。实验中定义了两个job,一个是busy job,它在运行期休眠了3S(<misfireThreshold ),另一个是TimeoutJob。我们为TimeoutJob定义了一个timeoutTrigger触发器,触发器每隔1S会运行一次TimeoutJob,总共运行7次。通过这样的设定,在busy job占用了线程后,timeout job的触发器已经超时,在3秒的运行期中timeout job触发器错过了3次作业运行时机。OK,下面运行代码看看调度器怎么处理这个问题。

//BusyJob.java
public class BusyJob implements Job {
	private final Logger logger = LoggerFactory.getLogger(BusyJob.class);
	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String jobName = context.getJobDetail().getKey().getName();
		logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 开始执行");
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 执行完毕");
	}
 
}
//TimeoutJob.java
public class TimeoutJob implements Job {
	private final Logger logger = LoggerFactory.getLogger(TimeoutJob.class);
	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {
		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 		String jobName = context.getJobDetail().getKey().getName();
		logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 开始执行");
		logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 执行完毕");
	}
 
}
//TimeoutButNotMisfireTest.java
/**
 * 触发器超时,但没有misfire
 */
public class TimeoutButNotMisfireTest {
	public static void main(String[] args) throws SchedulerException, InterruptedException {
		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
		// busy job
		JobDetail busyJob = JobBuilder //
				.newJob(BusyJob.class)//
				.withIdentity("busy job", "group1")//
				.build();
		SimpleTrigger busyTrigger = TriggerBuilder //
				.newTrigger() //
				.withIdentity("busy job trigger", "group1")//
				.startNow() //
				.withPriority(5) // 高优先级
				.withSchedule(SimpleScheduleBuilder.simpleSchedule() //
						.withRepeatCount(0) //
				).build();
		scheduler.scheduleJob(busyJob, busyTrigger);
		// timeout job
		JobDetail timeoutJob = JobBuilder //
				.newJob(TimeoutJob.class)//
				.withIdentity("timeout job", "group2")//
				.build();
		SimpleTrigger timeoutTrigger = TriggerBuilder //
				.newTrigger() //
				.withIdentity("timeout job trigger", "group2")//
				.startNow() //立即触发
				.withPriority(1) // 低优先级
				.withSchedule(SimpleScheduleBuilder.simpleSchedule() //
						.withIntervalInSeconds(1) //每隔1S触发一次
						.withRepeatCount(7) // 循环7次
				).build();
		scheduler.scheduleJob(timeoutJob, timeoutTrigger);
		scheduler.start();
		Thread.sleep(20 * 1000);
		scheduler.shutdown(true);
	}
}
INFO  11:31:11,420 com.github.thinwonton.quartz.sample.misfire.BusyJob: [busy job] 在  : [ 11:31:11] 开始执行
 INFO  11:31:14,420 com.github.thinwonton.quartz.sample.misfire.BusyJob: [busy job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 开始执行
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 开始执行
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:14,423 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 开始执行
 INFO  11:31:14,423 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:14,426 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 开始执行
 INFO  11:31:14,426 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 执行完毕
 INFO  11:31:15,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:15] 开始执行
 INFO  11:31:15,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:15] 执行完毕
 INFO  11:31:16,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:16] 开始执行
 INFO  11:31:16,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:16] 执行完毕
 INFO  11:31:17,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:17] 开始执行
 INFO  11:31:17,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:17] 执行完毕
 INFO  11:31:18,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:18] 开始执行
 INFO  11:31:18,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:18] 执行完毕

通过观察运行结果,我们可以得到结论:
超时的触发器(超时时间小于misfireThreshold)在获取到运行线程后,将会立即运行前面错过的所有作业job,然后按照前面制定的周期性任务正常运行。

3.3.3.2.timeout >= misfireThreshold
对于触发器超时,并且超时时间大于设定的misfireThreshold 这种情况,调度器引擎为简单触发器SimpleTrigger和表达式CronTrigger提供了多种处理策略,我们可以在定义触发器时指定需要的策略。
3.3.3.3.SimpleTrigger的处理策略
SimpleTrigger的Misfire策略常量:

1.MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
2.MISFIRE_INSTRUCTION_FIRE_NOW
3.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
4.MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
5.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
6.MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT

MISFIRE_INSTRUCTION_FIRE_NOW : 调度引擎在MisFire的情况下,将任务(JOB)马上执行一次。需要注意的是 这个指令通常被用做只执行一次的Triggers,也就是没有重复的情况(non-repeating),如果这个Triggers的被安排的执行次数大于0。那么这个执行与 ** MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT ** 相同。

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT: 调度引擎重新调度该任务,repeat count 保持不变,按照原有制定的执行方案执行repeat count次,但是,如果当前时间,已经晚于 end-time,那么这个触发器将不会再被触发。举个例子:比如一个触发器设置的时间是 10:00 执行时间间隔10秒 重复10次。那么当10:07秒的时候调度引擎可以执行这个触发器的任务,然后按照原有制定的时间间隔执行10次。但是如果触发器设置的执行时间是10:00,结束时间为10:10,由于种种原因导致该触发器在10:11分才能被调度引擎触发,这时,触发器将不会被触发了。

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略类似,唯一的区别就是调度器触发触发器的时间不是“现在” 而是下一个 scheduled time。

MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT: 这个策略跟上面的策略 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 比较类似,调度引擎重新调度该任务,repeat count 是剩余应该执行的次数,也就是说本来这个任务应该执行10次,但是已经错过了3次,那么这个任务就还会执行7次。

MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT 策略类似,区别就是repeat count 是剩余应该执行的次数而不是全部的执行次数。比如一个任务应该在2:00执行,repeat count=5,时间间隔5秒, 但是在2:07才获得执行的机会,那任务不会立即执行,而是按照机会在2点10秒执行。

MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY: 这个策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略类似,但这个策略是忽略所有的超时状态,快速执行之前错过的次数,然后再按照之前制定的周期触发触发器。举个例子,一个SimpleTrigger 每个15秒钟触发, 但是超时了5分钟才获得执行的机会,那么这个触发器会被快速连续调用20次, 追上前面落下的执行次数。
3.3.3.4.CronTrigger的处理策略
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: 指示触发器超时后会被立即安排执行。
MISFIRE_INSTRUCTION_DO_NOTHING: 这个策略与策略 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 正好相反,它不会被立即触发,而是获取下一个被触发的时间,并且如果下一个被触发的时间超出了end-time 那么触发器就不会被执行。
3.3.3.5.Trigger.MISFIRE_INSTRUCTION_SMART_POLICY
所有的trigger都有一个Trigger.MISFIRE_INSTRUCTION_SMART_POLICY策略可以使用,该策略也是所有trigger的默认策略。

如果使用smart policy,SimpleTrigger会根据实例的配置及状态,在所有MISFIRE策略中动态选择一种Misfire策略。
SimpleTrigger.updateAfterMisfire()的JavaDoc中解释了该动态行为的具体细节。
SimpleTrigger的misfire机制 默认的 
基于在创建SimpleTrigger时选择的MISFIRE_INSTRUCTION_XXX更新SimpleTrigger的状态。
如果失火指令设置为MISFIRE_INSTRUCTION_SMART_POLICY,则将使用以下方案:

•如果重复计数为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的misfire机制----默认的 
根据创建CronTrigger时选择的MISFIRE_INSTRUCTION_XXX更新CronTrigger的状态。

如果失火指令设置为MISFIRE_INSTRUCTION_SMART_POLICY,则将使用以下方案:
•指令将解释为MISFIRE_INSTRUCTION_FIRE_ONCE_NOW

在使用SimpleTrigger构造trigger时,misfire策略作为基本调度(simple schedule)的一部分进行配置(通过SimpleSchedulerBuilder设置):

    trigger = newTrigger()
        .withIdentity("trigger7", "group1")
        .withSchedule(simpleSchedule()
            .withIntervalInMinutes(5)
            .repeatForever()
            .withMisfireHandlingInstructionNextWithExistingCount())
        .build();

 

3.4.日历示例(calendar)


日历示例(calendar)
Quartz的Calendar对象(不是java.util.Calendar对象)可以在定义和存储trigger的时候与trigger进行关联。Calendar用于从trigger的调度计划中排除时间段。比如,可以创建一个trigger,每个工作日的上午9:30执行,然后增加一个Calendar,排除掉所有的商业节日。

任何实现了Calendar接口的可序列化对象都可以作为Calendar对象,Calendar接口如下:

package org.quartz;

public interface Calendar {

  public boolean isTimeIncluded(long timeStamp);

  public long getNextIncludedTime(long timeStamp);

}

注意到这些方法的参数类型为long。你也许猜到了,他们就是毫秒单位的时间戳。即Calendar排除时间段的单位可以精确到毫秒。你也许对“排除一整天”的Calendar比较感兴趣。Quartz提供的org.quartz.impl.HolidayCalendar类可以很方便地实现。

Calendar必须先实例化,然后通过addCalendar()方法注册到scheduler。如果使用HolidayCalendar,实例化后,需要调用addExcludedDate(Date date)方法从调度计划中排除时间段。以下示例是将同一个Calendar实例用于多个trigger:

HolidayCalendar cal = new HolidayCalendar();
cal.addExcludedDate( someDate );
cal.addExcludedDate( someOtherDate );

sched.addCalendar("myHolidays", cal, false);


Trigger t = newTrigger()
    .withIdentity("myTrigger")
    .forJob("myJob")
    .withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
    .modifiedByCalendar("myHolidays") // but not on holidays
    .build();

// .. schedule job with trigger

Trigger t2 = newTrigger()
    .withIdentity("myTrigger2")
    .forJob("myJob2")
    .withSchedule(dailyAtHourAndMinute(11, 30)) // execute job daily at 11:30
    .modifiedByCalendar("myHolidays") // but not on holidays
    .build();

// .. schedule job with trigger2


接下来的几个课程将介绍触发器的施工/建造细节。现在,只要认为上面的代码创建了两个触发器,每个触发器都计划每天触发。然而,在日历所排除的期间内发生的任何发射都将被跳过。


4.Job Stores 持久化


4.1.存储形式


org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore  存储在内存
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX 存储在常见的关系型数据库
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreCMT 数据存储在Terracotta服务器中  不常用


5.监听器


5.1.TriggerListeners和JobListeners


与触发相关的事件包括:触发器触发,触发失灵(在本文档的“触发器”部分中讨论),并触发完成(触发器关闭的作业完成)。
job相关事件包括:job即将执行的通知,以及job完成执行时的通知。
使用自己的Listeners
要创建一个listener,只需创建一个实现org.quartz.TriggerListener和/或org.quartz.JobListener接口的对象。然后,listener在运行时会向调度程序注册,并且必须给出一个名称(或者,他们必须通过他们的getName()方法来宣传自己的名字)。
为了方便起见,实现这些接口,您的类也可以扩展JobListenerSupport类或TriggerListenerSupport类,并且只需覆盖您感兴趣的事件。
listener与调度程序的ListenerManager一起注册,并配有描述listener希望接收事件的job/触发器的Matcher。
scheduler.getListenerManager().addJobListener(myJobListener,KeyMatcher.jobKeyEquals(new JobKey("myJobName","myJobGroup")));
静态导入,将上面的例子变成这样:
scheduler.getListenerManager().addJobListener(myJobListener, jobKeyEquals(jobKey("myJobName", "myJobGroup")));
添加对特定组的所有job感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));
添加对两个特定组的所有job感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));
添加对所有job感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, allJobs());
注册TriggerListeners的工作原理相同。

 

5.2.SchedulerListeners


SchedulerListeners非常类似于TriggerListeners和JobListeners,除了它们在Scheduler本身中接收到事件的通知 - 不一定与特定触发器(trigger)或job相关的事件。

与计划程序相关的事件包括:添加job/触发器,删除job/触发器,调度程序中的严重错误,关闭调度程序的通知等。
SchedulerListeners注册到调度程序的ListenerManager。SchedulerListeners几乎可以实现任何实现org.quartz.SchedulerListener接口的对象。

添加SchedulerListener:

scheduler.getListenerManager().addSchedulerListener(mySchedListener);
删除SchedulerListener:

scheduler.getListenerManager().removeSchedulerListener(mySchedListener);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值