Quartz可以用来做什么?
Quartz是一个任务调度框架。比如你遇到这样的问题
- 想每月25号,信用卡自动还款
- 想每年4月1日自己给当年暗恋女神发一封匿名贺卡
- 想每隔1小时,备份一下自己的爱情动作片 学习笔记到云盘
这些问题总结起来就是:在某一个有规律的时间点干某件事。并且时间的触发的条件可以非常复杂(比如每月最后一个工作日的17:50),复杂到需要一个专门的框架来干这个事。 Quartz就是来干这样的事,你给它一个触发条件的定义,它负责到了时间点,触发相应的Job起来干活。
一个简单的示例
这里面的所有例子都是基于Quartz 2.2.1
public class QuartzTest { public static void main(String[] args) { Quart(HelloQuartz.class,"*/1 * * * * ?"); } private static void Quart(Class<? extends Job> clazz,String cron) { try { //创建scheduler Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); //定义一个Trigger Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1") //定义name/group .startNow()//一旦加入scheduler,立即生效 // .withSchedule(simpleSchedule().withIntervalInSeconds(1).repeatForever()) //使用SimpleTrigger,每隔一秒执行一次,一直执行,奔腾到老不停歇 .withSchedule(CronScheduleBuilder.cronSchedule(cron)) .build(); //定义一个JobDetail JobDetail job = JobBuilder.newJob(clazz) //定义Job类为HelloQuartz类,这是真正的执行逻辑所在 .withIdentity("job1", "group1") //定义name/group .usingJobData("name", "Quart") //定义属性 .build(); //加入这个调度 scheduler.scheduleJob(job, trigger); //启动之 scheduler.start(); //运行一段时间后关闭 Thread.sleep(10000); scheduler.shutdown(true); } catch (Exception e) { e.printStackTrace(); } } }
public class HelloQuartz implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { JobDetail detail = context.getJobDetail(); String name = detail.getJobDataMap().getString("name"); System.out.println("say hello to " + name + " at " + new Date()); } }
个例子很好的覆盖了Quartz最重要的3个基本要素:
- Scheduler:调度器。所有的调度都是由它控制。
- Trigger: 定义触发的条件。例子中,它的类型是SimpleTrigger,每隔1秒中执行一次(什么是SimpleTrigger下面会有详述)。
- JobDetail & Job: JobDetail 定义的是任务数据,而真正的执行逻辑是在Job中,例子中是HelloQuartz。 为什么设计成JobDetail + Job,不直接使用Job?这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
关于name和group
JobDetail和Trigger都有name和group。
name是它们在这个sheduler里面的唯一标识。如果我们要更新一个JobDetail定义,只需要设置一个name相同的JobDetail实例即可。
group是一个组织单元,sheduler会提供一些对整组操作的API,比如 scheduler.resumeJobs()。
Trigger
在开始详解每一种Trigger之前,需要先了解一下Trigger的一些共性。
StartTime & EndTime
startTime和endTime指定的Trigger会被触发的时间区间。在这个区间之外,Trigger是不会被触发的。
** 所有Trigger都会包含这两个属性 **
优先级(Priority)
当scheduler比较繁忙的时候,可能在同一个时刻,有多个Trigger被触发了,但资源不足(比如线程池不足)。那么这个时候比剪刀石头布更好的方式,就是设置优先级。优先级高的先执行。
需要注意的是,优先级只有在同一时刻执行的Trigger之间才会起作用,如果一个Trigger是9:00,另一个Trigger是9:30。那么无论后一个优先级多高,前一个都是先执行。
优先级的值默认是5,当为负数时使用默认值。最大值似乎没有指定,但建议遵循Java的标准,使用1-10,不然鬼才知道看到【优先级为10】是时,上头还有没有更大的值。
Misfire(错失触发)策略
类似的Scheduler资源不足的时候,或者机器崩溃重启等,有可能某一些Trigger在应该触发的时间点没有被触发,也就是Miss Fire了。这个时候Trigger需要一个策略来处理这种情况。每种Trigger可选的策略各不相同。
这里有两个点需要重点注意:
- MisFire的触发是有一个阀值,这个阀值是配置在JobStore的。比RAMJobStore是org.quartz.jobStore.misfireThreshold。只有超过这个阀值,才会算MisFire。小于这个阀值,Quartz是会全部重新触发。
所有MisFire的策略实际上都是解答两个问题:
- 已经MisFire的任务还要重新触发吗?
- 如果发生MisFire,要调整现有的调度时间吗?
比如SimpleTrigger的MisFire策略有:
-
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY
这个不是忽略已经错失的触发的意思,而是说忽略MisFire策略。它会在资源合适的时候,重新触发所有的MisFire任务,并且不会影响现有的调度时间。
比如,SimpleTrigger每15秒执行一次,而中间有5分钟时间它都MisFire了,一共错失了20个,5分钟后,假设资源充足了,并且任务允许并发,它会被一次性触发。
这个属性是所有Trigger都适用。
-
MISFIRE_INSTRUCTION_FIRE_NOW
忽略已经MisFire的任务,并且立即执行调度。这通常只适用于只执行一次的任务。
-
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
将startTime设置当前时间,立即重新调度任务,包括的MisFire的
-
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT
类似MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT,区别在于会忽略已经MisFire的任务
-
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT
在下一次调度时间点,重新开始调度任务,包括的MisFire的
-
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
类似于MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT,区别在于会忽略已经MisFire的任务。
-
MISFIRE_INSTRUCTION_SMART_POLICY
所有的Trigger的MisFire默认值都是这个,大致意思是“把处理逻辑交给聪明的Quartz去决定”。基本策略是,
- 如果是只执行一次的调度,使用MISFIRE_INSTRUCTION_FIRE_NOW
- 如果是无限次的调度(repeatCount是无限的),使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT
- 否则,使用MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT
MisFire的东西挺繁杂的,可以参考这篇