Quartz中间件
Quartz相关组件介绍
1、quartz包含四个核心组件Scheduler、Trigger、Job、JobDetail
2、其中trigger和job、jobDetail为元数据,而Scheduler为实际进行调度的控制器
3、Trigger用于定义调度任务的时间规则,在Quartz中主要有四种类型的Trigger:SimpleTrigger、CronTrigger、DataIntervalTrigger和NthIncludedTrigger。
4、Quartz将任务分为Job、JobDetail两部分,其中Job用来定义任务的执行逻辑,而JobDetail用来描述Job的定义(例如Job接口的实现类以及其他相关的静态信息)。对Quartz而言,主要有两种类型的Job,StateLessJob、StateFulJob
5、实际执行调度逻辑的控制器,Quartz提供了DirectSchedulerFactory和StdSchedulerFactory等工厂类,用于支持Scheduler相关对象的产生。
保存的元数据
Table name | Description |
---|---|
QRTZ_CALENDARS | 存储Quartz的Calendar信息 |
QRTZ_CRON_TRIGGERS | 存储CronTrigger,包括Cron表达式和时区信息 |
QRTZ_FIRED_TRIGGERS | 存储与已触发的Trigger相关的状态信息,以及相联Job的执行信息 |
QRTZ_PAUSED_TRIGGER_GRPS | 存储已暂停的Trigger组的信息 |
QRTZ_SCHEDULER_STATE | 存储少量的有关Scheduler的状态信息,和别的Scheduler实例 |
QRTZ_LOCKS | 存储程序的悲观锁的信息 |
QRTZ_JOB_DETAILS | 存储每一个已配置的Job的详细信息 |
QRTZ_SIMPLE_TRIGGERS | 存储简单的Trigger,包括重复次数、间隔、以及已触的次数 |
QRTZ_BLOG_TRIGGERS | Trigger作为Blob类型存储 |
QRTZ_TRIGGERS | 存储已配置的Trigger的信息 |
常用API
Scheduler :用于与调度程序交互的主程序接口。
Job :预先定义的希望在未来时间被调度程序执行的任务类,自定义。
JobDetall :使用JobDetail来定义定时任务的实例,JobDetail实例是通过JobBuilder类创建。
JobDataMap :可包含数据对象,在job实例执行的是好,可使用包含的数据;JobDataMap是java Map接口的实现,增加了一些存取基本类型方法。
Trgger触发器 :Trigger对象是用于触发执行Job的,当调度一个Job时,我们实例一个触发器然后调整它的属性来满足Job执行的条件,表明任务在什么时候执行。定义了一个已经被安排的任务将在什么时候执行的时间条件,比如每秒执行一次。
JobBuilder :用于声明一个任务实例,也可以定义关于该任务的详情比如:任务名,组名等,这个声明的实例将作为一个实例执行的任务。
TriggerBuilder :触发器创建器,用于创建触发器trigger实例。
JobListener,TriggerListener,SchedulerListener监听器,用于对组件的监听。
Quartz的使用
1、创建项目并加入依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>xxx</version>
</dependency>
2、使用自带的sql脚本运行sql语句,导入初始的表单
3、创建作业PrintWordsJob
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public class PrintWordsJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));
}
}
4、创建scheduler调度器 使用simpleTrigger
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.concurrent.TimeUnit;
public class MyScheduler {
public static void main(String[] args) throws SchedulerException, InterruptedException {
// 1、创建JobDetail实例,并与PrintWordsJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)
.withIdentity("job1", "group1").build();
// 2、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1")
.startNow()//立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)//每隔1s执行一次
.repeatForever()).build();//一直执行
//3、创建调度器Scheduler并执行
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("--------scheduler start ! ------------");
scheduler.start();
//睡眠
TimeUnit.MINUTES.sleep(1);
scheduler.shutdown();
System.out.println("--------scheduler shutdown ! ------------");
}
}
Quartz相关组件详细描述
1、Job和JobDetail
Job是Quartz中的一个接口,接口下只有execute方法,在这个方法中编写业务逻辑。
JobDetail用来绑定Job,为Job实例提供许多属性:
name group jobClass jobDataMap
JobDetail绑定指定的Job,每次Scheduler调度执行一个Job的时候,首先会拿到对应的Job,然后创建该Job实例,再去执行Job中的execute()的内容,任务执行结束后,关联的Job对象实例会被释放,且会被JVM GC清除。
为什么设计成JobDetail + Job,不直接使用Job
JobDetail定义的是任务数据,而真正的执行逻辑是在Job中。 这是因为任务是有可能并发执行,如果Scheduler直接使用Job,就会存在对同一个Job实例并发访问的问题。而JobDetail & Job 方式,Sheduler每次执行,都会根据JobDetail创建一个新的Job实例,这样就可以规避并发访问的问题。
2、Trigger、SimpleTrigger、CronTrigger
Trigger
Trigger是Quartz的触发器,会去通知Scheduler何时去执行对应Job。
new Trigger().startAt():表示触发器首次被触发的时间;
new Trigger().endAt():表示触发器结束触发的时间;
SimpleTrigger
SimpleTrigger可以实现在一个指定时间段内执行一次作业任务或一个时间段内多次执行作业任务。
CronTrigger
CronTrigger功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基于Cron表达式的,先了解下Cron表达式: 由7个子表达式组成字符串的,格式如下:
[秒] [分] [小时] [日] [月] [周] [年]
3、JobListener
创建MyJobListener实现JobListener接口
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;
public class MyJobListener implements JobListener {
public String getName() {
return this.getClass().getSimpleName();
}
//Scheduler在jobDetail将要被执行时调用这个方法(执行前)
public void jobToBeExecuted(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("我的job名1:" + jobName);
}
//Scheduler在jobDetail即将被执行,但又被TriggerListermer 否定时会调用该方法
public void jobExecutionVetoed(JobExecutionContext context) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("我的job名2:" + jobName);
}
//Scheduler在jobDetail即将被执行之后调用这个方法。(执行后)
public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {
String jobName = context.getJobDetail().getKey().getName();
System.out.println("我的job名3:" + jobName);
}
}
在scheduler创建后加入监听即可生效
scheduler.getListenerManager().addJobListener(new MyJobListener());
4、TriggerListener
任务调度过程中,与触发器Trigger相关的事件包括:触发器触发,触发器未正常触发,触发器完成等。
import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;
public class MyTriggerListener implements TriggerListener {
//用于获取触发器的名称
public String getName() {//获取默认类名
return this.getClass().getSimpleName();
}
//当与监听器相关联的trigger被触发,job上的execute()方法将被执行时,Scheduler就调用该方法
public void triggerFired(Trigger trigger, JobExecutionContext context) {
System.out.println("triggerFired");
}
//在Trigger触发后,job将要被执行时由Scheduler调用这个方法。
//TriggerListener给一个选择去否决job的执行。如方法返回true,job此次将不会为trigger触发执行。false,放行。
public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {
System.out.println("vetoJobExecution");
return false;
}
//Scheduler 调用这个方法是在trigger错过时触发。
public void triggerMisfired(Trigger trigger) {
System.out.println("triggerMisfired");
}
//triggerComplete:trigger被触发并且完成了job的执行时,Scheduler调用这个方法。
public void triggerComplete(Trigger trigger, JobExecutionContext context,
Trigger.CompletedExecutionInstruction triggerInstructionCode) {
System.out.println("triggerComplete");
}
}
scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());
5、SchedulerListener
SchedulerListener会在scheduler的生命周期中关键事件发生时被调用,与Scheduler有关事件;增加或者删除一个 job/trigger,关闭scheduler等。
import org.quartz.*;
public class MySchedulerListener implements SchedulerListener {
//用于部署JobDetail 的时候调用
public void jobScheduled(Trigger trigger) {
String name = trigger.getKey().getName();
System.out.println("获取触发器名称:"+name);
}
//卸载JobDetail 的时候调用
public void jobUnscheduled(TriggerKey triggerKey) {
System.out.println(triggerKey.getName());
}
//当trigger来到再也不会触发的时候调用这个方法
public void triggerFinalized(Trigger trigger) {
String name = trigger.getKey().getName();
System.out.println("获取触发器名称:"+name);
}
//当trigger 被暂停时候调用
public void triggerPaused(TriggerKey triggerKey) {
System.out.println("被暂停1");
}
//当trigger组 被暂停时候调用
public void triggersPaused(String triggerGroup) {
System.out.println("被暂停2");
}
///当trigger从 被暂停 到恢复 时候 调用
public void triggerResumed(TriggerKey triggerKey) {
System.out.println("恢复");
}
///当trigger组从 被暂停 到恢复 时候 调用
public void triggersResumed(String triggerGroup) {
System.out.println("恢复");
}
//添加工作任务调用
public void jobAdded(JobDetail jobDetail) {
System.out.println("添加工作任务");
}
//删除工作任务
public void jobDeleted(JobKey jobKey) {
System.out.println("删除工作任务");
}
public void jobPaused(JobKey jobKey) {
// TODO Auto-generated method stub
}
public void jobsPaused(String jobGroup) {
// TODO Auto-generated method stub
}
public void jobResumed(JobKey jobKey) {
// TODO Auto-generated method stub
}
public void jobsResumed(String jobGroup) {
// TODO Auto-generated method stub
}
//scheduler产生Error调用
public void schedulerError(String msg, SchedulerException cause) {
// TODO Auto-generated method stub
}
//scheduler被挂起时候调用
public void schedulerInStandbyMode() {
// TODO Auto-generated method stub
}
//scheduler开启的时候调用
public void schedulerStarted() {
System.out.println("scheduler kai qi ");
}
//scheduler 正在开启的时候调用 ing.....
public void schedulerStarting() {
System.out.println("scheduler 正在开启的时候调用 ing.....");
}
scheduler关闭 的时候调用
public void schedulerShutdown() {
System.out.println("scheduler关闭 的时候调用");
}
//scheduler 正在关闭的时候调用 ing.....
public void schedulerShuttingdown() {
System.out.println("//scheduler 正在关闭的时候调用 ing.....");
}
//scheduler 数据被清除了的时候调用
public void schedulingDataCleared() {
System.out.println("//scheduler 数据被清除了的时候调用");
}
}
scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());
Cron表达式
1.cron表达式:
cron表达式是由若干数字、空格、符号按一定的规则,组成一组字符串,从而表达时间的信息。与正则表达式类似,都是一个字符串表示一些信息。
2.cron表达式标准结构:
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义。
corn从左到右(用空格隔开):秒 分 小时 月份中的日期 月份 星期中的日期 年份
3.Cron有如下两种语法格式:
Seconds Minutes Hours DayofMonth Month DayofWeek Year
Seconds Minutes Hours DayofMonth Month DayofWeek
4.每个字段的含义:
5.特殊字符:
“?”字符:表示不确定的值
“,”字符:指定数个值
“-”字符:指定一个值的范围
“/”字符:指定一个值的增加幅度。n/m表示从n开始,每次增加m
“L”字符:用在日表示一个月中的最后一天,用在周表示该月最后一个星期X
“W”字符:指定离给定日期最近的工作日(周一到周五)
“#”字符:表示该月第几个周X。6#3表示该月第3个周五
简单的结构图
详细的时序图
任务调度步骤
步骤1.用户首先需要生成一个调度器工厂SchedulerFactory,可以用下面的方式实现自己的定制化:
1 Properties properties=new Properties(); properties.put("org.quartz.threadPool.class","org.quartz.simpl.SimpleThreadPool"); 2 properties.put("org.quartz.threadPool.threadCount","10"); 3 SchedulerFactory sf=new StdSchedulerFactory(properties);
步骤2.然后通过getScheduler()方法从调度器工厂里得到调度器实例,首先查找有没有这样的调度器,没有的话,就生成一个,有的话直接返回。所以得到的一般是单例,即默认的调度器。
步骤3.Scheduler有一个QuartzSchedulerThread(Thread的子类)属性,在scheduler实例化的时候,实例化了一个对象,并用ThreadExecutor启动该线程对象。该线程就是调度线程,主要任务就是不停的从JobStore中获取即将被触发的触发器(默认30s调度一次)。在这个时候调度线程虽然启动,但是处于pause状态。
步骤4.接下来是任务调度的部分:
1 Scheduler scheduler=sf.getScheduler(); 2 scheduler.addJobListener(new TaskListener()); 3 scheduler.scheduleJob(jobDetail, simpleTrigger); 4 scheduler.start();
client通过scheduleJob()方法将任务和触发器存储在JobStore中,通过start()方法将QuartzSchedulerThread的pause状态设为false,通知调度线程执行任务,此后调度线程不停的从JobStore中去取即将触发的任务。
任务执行步骤
步骤1.调度线程首先去线程池中获取可用的线程,如果没有的话,就阻塞。
步骤2.从JobStore(从存储介质中获取触发器,存储介质可以是内存也可以是数据库)获取(接下来30s内的)触发器,然后等待该触发器触发。
步骤3.调度线程创建一个JobRunShell(就是一个Runnable),然后从线程池中调用线程执行该任务。
接下来就是任务执行的时序:
步骤4.获取trigger、JobDetail以及生成Job实例,然后执行job的execute接口函数。