介绍
Quartz是一个任务调度框架。
The key interfaces of the Quartz API are:
Scheduler -与调度程序交互的主要API。
Job -由希望由调度程序执行的组件实现的接口。真正执行的逻辑。
JobDetail- 用于定义作业的实例。
Trigger -定义执行给定Job的时间表的组件。
JobBuilder -用于定义/构建定义作业实例的JobDetail实例。
TriggerBuilder -用于定义/构建触发器实例。
一个调度程序的生命周期是由它为界的创作,通过SchedulerFactory和其通话关闭()方法。一旦创建了调度器接口,就可以使用添加,删除和列出作业和触发器,并执行其他与调度相关的操作(例如暂停触发器)。但是,调度程序实际上不会对任何触发器(执行作业)执行操作,直到它已使用start()方法启动。
关于name和group
JobDetail和Trigger都有name和group。
name是它们在这个sheduler里面的唯一标识。如果我们要更新一个JobDetail定义,只需要设置一个name相同的JobDetail实例即可。
group是一个组织单元,sheduler会提供一些对整组操作的API,比如scheduler.resumeJobs()。
StartTime & EndTime
startTime和endTime指定的Trigger会被触发的时间区间。在这个区间之外,Trigger是不会被触发的。
优先级(Priority)
当scheduler比较繁忙的时候,可能在同一个时刻,有多个Trigger被触发了,但资源不足(比如线程池不足),优先级高的先执行。任何整数值都允许优先级为正数或负数。建议设定一个优先级区间比如1-10,避免你永远都不知道的最大优先级。
Misfire(错失触发)策略
类似的Scheduler资源不足的时候,或者机器崩溃重启等,有可能某一些Trigger在应该触发的时间点没有被触发,也就是MissFire了。这个时候Trigger需要一个策略来处理这种情况。每种Trigger可选的策略各不相同。
MisFire的触发是有一个阀值,这个阀值是配置在JobStore的。只有超过这个阀值,才会算MisFire。小于这个阀值,Quartz是会全部重新触发。
所有MisFire的策略实际上都是解答两个问题:
已经MisFire的任务还要重新触发吗?
如果发生MisFire,要调整现有的调度时间吗?
默认使用的时MISFIRE_INSTRUCTION_SMART_POLICY(智能策略):
如果是只执行一次的调度,使用MISFIRE_INSTRUCTION_FIRE_NOW,忽略已经MisFire的任务,并且立即执行调度。
如果是无限次的调度(repeatCount是无限的),使用MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT,将startTime设置当前时间,重新开始调度任务,忽略已经misfire的任务。
否则,使用MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT,在下一次调度时间点,重新开始调度任务,忽略已经misfire的任务。
具体参考:https://dzone.com/articles/quartz-scheduler-misfire
Calendar
用于补充Trigger的时间。可以排除或加入某一些特定的时间点。
以”每10秒执行一次“为例,我们想排除掉每分钟20秒这个时间点。这个时间,就可以用Calendar来实现。
CronCalendar cal = new CronCalendar("20 * * * * ?");
QuartzScheduleMgr.getInstance().addCalendar("cronCal", cal, false, false);
Trigger trigger = newTrigger().withIdentity("trigger", "group1") // 定义name/group
.startNow()// 一旦加入scheduler,立即生效
.modifiedByCalendar("cronCal")
.withSchedule(cronSchedule("0/10 * * * * ?")) // 使用CronTrigger
.build();
// 定义一个JobDetail
JobDetail job = newJob(HelloQuartz.class) // 定义Job类为HelloQuartz类,这是真正的执行逻辑所在
.withIdentity("job1", "group1") // 定义name/group
.usingJobData("name", "quartz1") // 定义属性
.build();
// 加入这个调度
QuartzScheduleMgr.scheduleJob(job, trigger);
// 启动之
QuartzScheduleMgr.start();
其他calender
HolidayCalendar。指定特定的日期,比如20140613。精度到天。
DailyCalendar。指定每天的时间段(rangeStartingTime,rangeEndingTime),格式是HH:MM[:SS[:mmm]]。也就是最大精度可以到毫秒。
WeeklyCalendar。指定每星期的星期几,可选值比如为java.util.Calendar.SUNDAY。精度是天。
MonthlyCalendar。指定每月的几号。可选值为1-31。精度是天
AnnualCalendar。指定每年的哪一天。使用方式如上例。精度是天。
CronCalendar。指定Cron表达式。精度取决于Cron表达式,也就是最大精度可以到秒。
CronTrigger
适合于更复杂的任务,它支持类型于LinuxCron的语法(并且更强大)。基本上它覆盖了SimpleTrigger、CalendarIntervalTrigger、DailyTimeIntervalTrigger的绝大部分能力。
cron格式说明:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/crontrigger.html
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
简单例子:
HelloQuartz.class
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
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());
}
}
测试代码:
public void testStartSchedule() {
try {
// 创建scheduler
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Trigger trigger = newTrigger().withIdentity("trigger", "group1") // 定义name/group
.startNow()// 一旦加入scheduler,立即生效
.withSchedule(cronSchedule("0/10 * * * * ?")) // 使用CronTrigger
.build();
// 定义一个JobDetail
JobDetail job = newJob(HelloQuartz .class) // 定义Job类为HelloQuartz类,这是真正的执行逻辑所在
.withIdentity("job1", "group1") // 定义name/group
.usingJobData("name", "quartz1") // 定义属性
.build();
// 加入这个调度
scheduler.scheduleJob(job, trigger);
// 删除相同key的trigger并保存新的trigger
scheduler.rescheduleJob(trigger.getKey(), trigger);
// 启动之
scheduler.start();
} catch (Exception e) {
e.printStackTrace();
}
}
public void testUpdateSchedule() {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
Trigger trigger = newTrigger().withIdentity("trigger", "group1") // 定义name/group
.startNow()// 一旦加入scheduler,立即生效
.withSchedule(cronSchedule("0/3 * * * * ?")) // 使用CronTrigger
.build();
// 更新trigger
scheduler.rescheduleJob(trigger.getKey(), trigger);
} catch (Exception e) {
e.printStackTrace();
}
}
public void testPauseSchedule() {
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
JobKey key = new JobKey("job1", "group1");
scheduler.pauseJob(key);
System.out.println("pause");
} catch (SchedulerException e) {
e.printStackTrace();
}
}
job的并发性
@DisallowConcurrentExecution是一个注释,可以添加到Job类中,告诉Quartz不要同时执行给定作业定义(指给定作业类)的多个实例。如果运行时间大于Quartz周期,Quartz会让正在运行的job结束以后,立即执行下一个。
@PersistJobDataAfterExecution是一个注释,可以添加到Job类中,告诉Quartz在execute()方法成功完成后(不抛出异常)更新JobDetail的JobDataMap的存储副本,以便下次执行同一作业JobDetail接收更新的值而不是最初存储的值。像 @DisallowConcurrentExecution注解一样,这适用于作业定义实例,而不是作业类实例,尽管它决定让作业类携带该属性,因为它通常会影响类的编码方式(例如,“有状态'需要被执行方法内的代码明确'理解')。
如果使用@PersistJobDataAfterExecution注释,则应该强烈考虑同时使用 @DisallowConcurrentExecution注释,以避免同时执行同一作业的两个实例(JobDetail)时可能会混淆(竞争条件)哪些数据存储在哪里。
SchedulerListeners
SchedulerListeners与TriggerListener和JobListeners非常相似,只是它们接收Scheduler本身内的事件通知- 不一定与特定触发器或作业相关的事件。
与调度程序相关的事件包括:添加作业/触发器,删除作业/触发器,调度程序中出现严重错误,调度程序正在关闭的通知等。
scheduler.getListenerManager().addSchedulerListener(mySchedListener);
job运行时暂停,不会影响当前运行。
文档:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/quick-start.html
中文博客:https://www.cnblogs.com/drift-ice/p/3817269.html
public class QuartzScheduleMgr {
private static Scheduler scheduler = getInstance();
/**
* 创建一个调度对象
*
* @return
* @throws SchedulerException
*/
public static Scheduler getInstance() {
if (scheduler == null) {
SchedulerFactory sf = new StdSchedulerFactory();
try {
scheduler = sf.getScheduler();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
return scheduler;
}
/**
* 启动一个调度对象
*
* @throws SchedulerException
*/
public static void start() throws SchedulerException {
scheduler.start();
}
/**
* 检查调度是否启动
*
* @return
* @throws SchedulerException
*/
public static boolean isStarted() throws SchedulerException {
return scheduler.isStarted();
}
/**
* 关闭调度信息
*
* @param waitForJobsToComplete
* 是否等待所有job执行完毕
* @throws SchedulerException
*/
public static void shutdown(boolean waitForJobsToComplete) throws SchedulerException {
scheduler.shutdown(waitForJobsToComplete);
}
/**
* 添加调度的job信息
*
* @param jobdetail
* @param trigger
* @return
* @throws SchedulerException
*/
public static void scheduleJob(JobDetail jobdetail, Trigger trigger) throws SchedulerException {
if (!checkJobExists(jobdetail.getKey())) {
scheduler.scheduleJob(jobdetail, trigger);
updateTrigger(trigger);
}
}
/**
* 暂停一个job任务
*
* @param jobkey
* @throws SchedulerException
*/
public static void pauseJob(JobKey jobKey) throws SchedulerException {
if (checkJobExists(jobKey)) {
scheduler.pauseJob(jobKey);
}
}
/**
* 恢复一个job任务
*
* @param jobKey
* @throws SchedulerException
*/
public static void resumeJob(JobKey jobKey) throws SchedulerException {
if (checkJobExists(jobKey)) {
scheduler.resumeJob(jobKey);
}
}
/**
* 删除job并取消其所有触发器
*
* @param jobkey
* @return
* @throws SchedulerException
*/
public static boolean deleteJob(JobKey jobKey) throws SchedulerException {
if (checkJobExists(jobKey)) {
return scheduler.deleteJob(jobKey);
}
return false;
}
/**
* 检查job是否存在
*
* @param jobKey
* @return
* @throws SchedulerException
*/
public static boolean checkJobExists(JobKey jobKey) throws SchedulerException {
return scheduler.checkExists(jobKey);
}
/**
* 检查trigger是否存在
*
* @param triggerKey
* @return
* @throws SchedulerException
*/
public static boolean checkTriggerExists(TriggerKey triggerKey) throws SchedulerException {
return scheduler.checkExists(triggerKey);
}
/**
* 更新指定Trigger(确保新Trigger的key和旧的一致)
*
* @param trigger
* @return
*/
public static Date updateTrigger(Trigger trigger) throws SchedulerException {
if (checkTriggerExists(trigger.getKey())) {
return scheduler.rescheduleJob(trigger.getKey(), trigger);
}
return null;
}
/**
* 更新指定job(确保新job的key和旧的一致)
*
* @param job
* @throws SchedulerException
*/
public static void updateJob(JobDetail job) throws SchedulerException {
if (checkJobExists(job.getKey())) {
scheduler.addJob(job, true);
}
}
/**
* 获取指定Job的所有Trigger
*
* @param jobKey
* @return
* @throws SchedulerException
*/
@SuppressWarnings("unchecked")
public static List<Trigger> getTriggersOfJob(JobKey jobKey) throws SchedulerException {
return (List<Trigger>) scheduler.getTriggersOfJob(jobKey);
}
/**
* 获取指定group的所有JobKey
*
* @param groupName
* @return
* @throws SchedulerException
*/
public static Set<JobKey> getJobByGroup(String groupName) throws SchedulerException {
return scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName));
}
/**
* 获取所有job
*
* @return
*/
public static Set<JobKey> getAllJobs() throws SchedulerException {
List<String> groups = scheduler.getJobGroupNames();
Set<JobKey> jobs = new HashSet<JobKey>();
for (String groupName : groups) {
jobs.addAll(getJobByGroup(groupName));
}
return jobs;
}
/**
* 获取当前正在运行的jobkey
*
* @return
* @throws SchedulerException
*/
public static List<JobKey> getCurrentExcutingJobs() throws SchedulerException {
List<JobKey> keys = new ArrayList<JobKey>();
List<JobExecutionContext> jobContexts = scheduler.getCurrentlyExecutingJobs();
for (JobExecutionContext context : jobContexts) {
keys.add(context.getTrigger().getJobKey());
}
return keys;
}
/**
* 获取trigger状态
*
* @param triggerKey
* @return
* @throws SchedulerException
*/
public static TriggerState getTriggerStatus(TriggerKey triggerKey) throws SchedulerException {
// state的值代表该任务触发器的状态:
// NONE -1 Trigger已经完成,且不会在执行,或者找不到该触发器,或者Trigger已经被删除
// NORMAL 0 正常状态(运行或者就绪都是这个状态)
// PAUSED 1 暂停状态
// COMPLETE 2 触发器完成,但是任务可能还正在执行中
// ERROR 3 错误
// BLOCKED 4 线程阻塞状态
return scheduler.getTriggerState(triggerKey);
}
}