众所周知,Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,在众多的中小项目中有着广泛的使用(本人所在的项目也有部分使用),Quartz的一个特点就是,引用简单,能和既有的框架做快速的整合,不管是基于spring的项目还是springboot的项目,甚至是简单的web项目,都可以快速的引入,加上学习成本低,对于大多数小伙伴来说,是个不错的选择
Quartz基本概念
Quartz 是一个完全由 Java 编写的开源作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制
Quartz 可以与 J2EE 与 J2SE 应用程序相结合也可以单独使用
Quartz 允许程序开发人员根据时间的间隔来调度作业
Quartz 实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联
Quartz 核心概念
在正式开始编写代码之前,有必要了解一些关于Quartz的几个核心概念,有助于我们对于代码的编写和理解
Job 表示一个工作,要执行的具体内容。此接口中只有一个方法,如下:
void execute(JobExecutionContext context)
JobDetail 表示一个具体的可执行的调度程序,Job 是这个可执行程调度程序所要执行的内容,另外 JobDetail 还包含了这个任务调度的方案和策略
Trigger 代表一个调度参数的配置,什么时候去调
Scheduler 代表一个调度容器,一个调度容器中可以注册多个 JobDetail 和 Trigger。当 Trigger 与 JobDetail 组合,就可以被 Scheduler 容器调度了
Quartz的运行环境
Quartz 可以运行嵌入在另一个独立式应用程序
Quartz 可以在应用程序服务器(或 servlet 容器)内被实例化,并且参与 XA 事务
Quartz 可以作为一个独立的程序运行(其自己的 Java 虚拟机内),可以通过 RMI 使用
Quartz 可以被实例化,作为独立的项目集群(负载平衡和故障转移功能),用于作业的执行
下面让我们正式开始编码吧
1、引入quartz依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--quartz相关依赖-->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
按照上面的核心概念中提到的,其实我们定义一个job在quartz中非常简单,大概2个步骤,第一一个实现Job接口的类,第二需要一个类,用于组装job,JobDetail,Trigger,然后利用调度器Scheduler将他们整合在一起就可以了,里面的细节就是如何运用API的过程,那么先看第一个例子吧
简单job类
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.time.LocalTime;
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("我正在执行任务:" + LocalTime.now());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Test1 {
public static void main(String[] args) throws SchedulerException {
//定义Scheduler对象
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
//定义JobDetail对象
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("jodDetail1", "group1").build();
//定义Trigger对象
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.startNow()
.withSchedule(
SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
.build();
//使用scheduler将JobDetail和Trigger进行组装
scheduler.scheduleJob(jobDetail,trigger);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.shutdown();
}
}
关于这段代码有几处需要简单说明下
JobDetail -> withIdentity ,为每一个job指定一个唯一的身份标识,最好带有一定的业务含义
Trigger -> withSchedule ,在这个模块中,定义运行的job的详细参数,比如schedule类型,有simpleSchedule简单类型,还有cronSchedule类型,withIntervalInSeconds任务执行间隔时间,这里面的API比较多,定义的时间策略也很多,有兴趣的同学可以点出来瞧瞧
本段代码的意思就是间隔5秒执行一次job
下面运行下这段代码,看下效果
job参数传递
试想有这么一种场景,每次执行job时,需要从jobDetail中给job传递一些参数,以便执行时使用该怎么做呢?
在jobDetail的对象构造过程中,提供了usingJobData的方法,里面有多种类型可供选择,最常见的像key,value类似map的结构
比如我们在jobDetail中传递一个name=jike的值,简单改造后如下:
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
JobDetail jobDetail = JobBuilder.newJob(MyJob2.class)
.withIdentity("jodDetail1", "group1")
.usingJobData("name","jike")
.build();
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.startNow()
.withSchedule(
SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
.build();
scheduler.scheduleJob(jobDetail,trigger);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.shutdown();
}
参数放进去了以后,job中怎么接收呢?quartz提供了大概3种方式进行参数的传递,
第一种,属性的set/get方法
public class MyJob2 implements Job {
@Getter@Setter
private String name;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
//JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
//System.out.println("name is :" + jobDataMap.getString("name"));
System.out.println("name is :" + name);
}
}
运行上面的代码
第二种,从excute的context中获取
public class MyJob2 implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
System.out.println("name is :" + jobDataMap.getString("name"));
}
}
还有一种场景,就是我希望这一次job执行完毕之后,参数也随着job的执行不断的改变,这时就需要@PersistJobDataAfterExecution 这个注解排上用场了,看下面的这段代码,第一次在jobDetail中向job传递了一个count的参数,后面我们希望每次job执行完毕后count数值递增
public class Test3 {
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
int count = new Random().nextInt(10);
JobDetail jobDetail = JobBuilder.newJob(MyJob3.class)
.withIdentity("jodDetail1", "group1")
.usingJobData("count",count)
.build();
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.startNow()
.withSchedule(
SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
.build();
scheduler.scheduleJob(jobDetail,trigger);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.shutdown();
}
}
@PersistJobDataAfterExecution
public class MyJob3 implements Job {
@Setter@Getter
private int count;
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
count++;
context.getJobDetail().getJobDataMap().put("count",count);
LocalTime now = LocalTime.now();
System.out.println("current time is :" + now.toString() + ",the cout is:" + count);
}
}
简单来说,@PersistJobDataAfterExecution这个注解具有存储参数的功能
trigger优先级
在使用trigger的时候,有这么一个问题不容忽略,就是当项目中有多个地方的job配置了同一时间点触发,但我们知道,quartz的执行是需要额外占用系统资源的,也就是每次job的执行需要系统提供新的线程来执行,往往在开发过程中,为了不至于让job的运行占用过多的线程资源,我们将quartz的线程开销数设置为一个固定的值,设置也比较简单,只需要在resource目录下,提供一个quartz.properties的文件进行简单的配置即可,job在启动的时候,会去读取这个配置文件中的内容
org.quartz.scheduler.instanceName=myScheduler
#配置quartz的线程总数,这里配置为1,放大问题出现的可能性
org.quartz.threadPool.threadCount=1
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
org.quartz.jobStore.misfire.Threshold=1000
这样以来,在上面的场景下,多个任务要在同一时间点触而且我们希望某个job的业务先执行怎么办呢?
quartz在trigger的配置中提供了优先级的API,现在假如有两个job,均在10秒之后触发,那么我们可以通过设置withPriority的值来达到我们的目的(注意:前提是线程资源不够的情况下,优先级才会生效)
public class Test4 {
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
JobDetail jobDetail1 = JobBuilder.newJob(MyJob4.class).withIdentity("jodDetail1", "group1").build();
JobDetail jobDetail2 = JobBuilder.newJob(MyJob4.class).withIdentity("jobDetail2", "group2").build();
Date date = DateBuilder.futureDate(10, DateBuilder.IntervalUnit.SECOND);
SimpleTrigger trigger1 = TriggerBuilder.newTrigger()
// .startNow()
.startAt(date)
.withPriority(2)
.usingJobData("msg","trigger1触发")
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();
SimpleTrigger trigger2 = TriggerBuilder.newTrigger()
//.startNow()
.startAt(date)
.withPriority(9)
.usingJobData("msg","trigger2触发")
.withSchedule(
SimpleScheduleBuilder
.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();
scheduler.scheduleJob(jobDetail1,trigger1);
scheduler.scheduleJob(jobDetail2,trigger2);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.shutdown();
}
}
public class MyJob4 implements Job {
@Setter@Getter
private String msg;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("我正在执行任务:" + LocalTime.now() +",msg is:" + msg);
}
}
简单改造一下代码之后,运行上面的程序,通过控制台打印结果可以发现具有更高优先级的trigger的任务,在资源有限的情况下会更先执行,
cronSchedule
相比simpleSchedule,在项目开发中cronSchedule的使用可能更多,cronSchedule的方式支持丰富的cron表达式,即我们熟悉的基于各种时间类型的定时任务,比如每隔5秒执行,就可以使用我们熟悉的 “0/5 * * * * ?”
使用CronTrigger,可以指定具体的时间表,例如“每周五中午”或“每个工作日和上午9:30”,甚至“每周一至周五上午9:00至10点之间每5分钟”和1月份的星期五“。
在CronScheduleBuilder对象的API中,提供了几种常用的和cron相关的方法,比如最常用的支持cron时间表达式的字符串格式的方法
请参考如下示例代码:
public class Test6 {
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
JobDetail jobDetail = JobBuilder.newJob(MyJob6.class).withIdentity("jodDetail1", "group1").build();
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.startNow()
.usingJobData("msg", "trigger1触发")
.withSchedule(
CronScheduleBuilder.cronSchedule("0/5 * * * * ?")
)
.build();
scheduler.scheduleJob(jobDetail, cronTrigger);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.shutdown();
}
}
public class MyJob6 implements Job {
@Setter@Getter
private String msg;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("我正在执行任务:" + LocalTime.now() +",msg is:" + msg);
}
}
运行效果如下:
misfire机制
在quartz中,有这么一种场景不能忽视,设想当你的任务间隔执行时间是5秒,但实际上,在job执行业务逻辑过程中却执行了9秒甚至更长的时间,那么对于quartz来说,这两次间隔的任务该怎么处理呢?因为人家原本希望的是每5秒一次的,现在你的业务执行时间打破了5秒的机制,要怎么处理呢?
在quartz中,针对cron类型的trigger,提供了下面3种处理方式,分别是:
withMisfireHandlingInstructionDoNothing 不触发立即执行,等待下次Cron触发频率到达时刻开始按照Cron频率依次执行
withMisfireHandlingInstructionIgnoreMisfires 以错过的第一个频率时间立刻开始执行,做错过的所有频率周期后,当下一次触发频率发生时间大于当前时间后,再按照正常的Cron频率依次执行
withMisfireHandlingInstructionFireAndProceed 以当前时间为触发频率立刻触发一次执行,后按照Cron频率依次执行
大概是什么意思呢?我们先用程序简单模拟下效果吧,比如使用第一种来看一下实际效果怎样的?withMisfireHandlingInstructionDoNothing
public class Test7 {
public static void main(String[] args) throws SchedulerException {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
JobDetail jobDetail1 = JobBuilder.newJob(Job7.class).withIdentity("jodDetail1", "group1").build();
CronTrigger cronTrigger = TriggerBuilder.newTrigger()
.startNow()
.usingJobData("msg", "trigger1触发")
.withPriority(2)
.withSchedule(
CronScheduleBuilder.cronSchedule("0/5 * * * * ?")
.withMisfireHandlingInstructionDoNothing()
)
.build();
scheduler.scheduleJob(jobDetail1, cronTrigger);
try {
Thread.sleep(600000);
} catch (InterruptedException e) {
e.printStackTrace();
}
scheduler.shutdown();
}
}
public class Job7 implements Job {
@Setter
@Getter
private String msg;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println("我正在执行任务:" + LocalTime.now() +",msg is:" + msg);
try {
Thread.sleep(7000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行程序,对照上述关于这几种misFire的解释,能够想象出下面的效果吗?
阅读量:617
点赞量:0
收藏量:0