Quartz分布式调度框架使用总结

一、Quartz简介

Quartz的三大组件:

  • Trigger:确定触发的时间和方式
  • JobDetail:具体的任务实现
  • Scheduler:任务调度的主题

Job和Trigger,是将任务本身与任务执行策略进行解耦,这样可以方便的实现N个任务和M个执行策略自由组合。而Scheduler相当于一个指挥官,从全局做调度,比如监听哪些Trigger已经ready,分配线程等。

二、Trigger触发器

Quartz有四类触发器:

  • SimpleTrigger
  • CronTrigger
  • CalendarIntervalTrigger
  • DailyTimeIntervalTrigger

由于本人最常用的是CronTrigger,以下介绍它的使用方法:

1
2
3
4
5
6
7
8
String cron = "0 00 10 * * ?";
CronTrigger cronTrigger = TriggerBuilder
.newTrigger()
.withIdentity("cronTrigger")
.forJob("job01", "group01")
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.startNow()
.build();

这里运用了构建者模式,有关Trigger的类图如下:
Trigger

三、Job和JobDetail 组件

JobDetail是任务的定义,在作业调度中使用,而Job是任务的执行逻辑。在JobDetail里会引用一个Job Class定义。

JobBuilder构造器来构造具体的JobDetail。两个接口+一个构造者模式的JobDetail构造器完成了具体业务Job和实际作业调度中的JobDetail分离。实际应用中只要面向JobDetail和Job两个接口编程,隐藏了具体的实现如:JobDetailImpl类。

1
2
3
4
5
6
7
8
JobDetail job=newJob()
.ofType(SampleJob.class)
.withIdentity("job01", "group01")
.withDescription("this is a test job")
.usingJobData("age", 18)
.build();

job.getJobDataMap().put("name", "quertz");
1
2
3
4
5
public class SampleJob implements Job {
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("hello quartz...");
}
}

要定义一个任务,需要干几件事:

  • 1.创建一个org.quartz.Job的实现类,并实现实现自己的业务逻辑。比如上面的SampleJob
  • 2.定义一个JobDetail,引用这个实现类
  • 3.加入scheduleJob

Quartz调度一次任务,会干如下的事:

  • 1.JobClass jobClass=JobDetail.getJobClass()
  • 2.Job jobInstance=jobClass.newInstance()。所以Job实现类,必须有一个public的无参构建方法。
  • 3.jobInstance.execute(JobExecutionContext context)。JobExecutionContext是Job运行的上下文,可以获得Trigger、Scheduler、JobDetail的信息。

也就是说,每次调度都会创建一个新的Job实例,这样的好处是有些任务并发执行的时候,不存在对临界资源的访问问题——当然,如果需要共享JobDataMap的时候,还是存在临界资源的并发访问的问题。

JobDataMap
每一个JobDetail都会有一个JobDataMap。JobDataMap本质就是一个Map的扩展类,只是提供了一些更便捷的方法,比如getString()之类的。

我们可以在定义JobDetail,加入属性值,方式有二:

  • 1.newJob().usingJobData(“age”, 18)
  • 2.job.getJobDataMap().put(“name”, “quertz”);

然后在Job中可以获取这个JobDataMap的值,方式同样有二:

  1. 方法一:从上下文中获取

    1
    2
    3
    4
    5
    6
    public void execute(JobExecutionContext context) throws JobExecutionException {
    JobDetail detail = context.getJobDetail();
    JobDataMap map = detail.getJobDataMap();
    System.out.println("say hello to " + name + "[" + map.getInt("age") + "]" + " at "
    + new Date());
    }
  2. 方法二:属性的setter方法,会将JobDataMap的属性自动注入

    1
    2
    3
    public void setName(String name) { 
    this.name = name;
    }

对于同一个JobDetail实例,执行的多个Job实例,是共享同样的JobDataMap,也就是说,如果你在任务里修改了里面的值,会对其他Job实例(并发的或者后续的)造成影响。

除了JobDetail,Trigger同样有一个JobDataMap,共享范围是所有使用这个Trigger的Job实例。

Job并发
Job是有可能并发执行的,比如一个任务要执行10秒中,而调度算法是每秒中触发1次,那么就有可能多个任务被并发执行。

有时候我们并不想任务并发执行,比如这个任务要去”获得数据库中所有未发送邮件的名单“,如果是并发执行,就需要一个数据库锁去避免一个数据被多次处理。这个时候一个@DisallowConcurrentExecution解决这个问题。

1
2
3
4
@DisallowConcurrentExecution
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("hello quartz...");
}

@PersistJobDataAfterExecution: 将这一次Job执行的结果保存在JobDetail中,下一次Job执行的时候可以访问。

注意,@DisallowConcurrentExecution是对JobDetail实例生效,也就是如果你定义两个JobDetail,引用同一个Job类,是可以并发执行的。

JobExecutionException
Job.execute()方法是不允许抛出除JobExecutionException之外的所有异常的(包括RuntimeException),所以编码的时候,最好是try-catch住所有的Throwable,小心处理。

Job其他相关特性
1)Durability — 如果一个Job是非持久化,那么这个Job会自动被删除当没有处于激活状态的促发器和他绑定。也就是说非持久化的Job的生命周期和相关的触发器的生命周期一样。

2)RequestsRecovery — 如果一个Job是RequestsRecovery的,那么当机器被意外关闭 或者scheduler被意外关闭重启之后,Job会自动重新执行(问题:Job执行了一半)

四、Scheduler 组件

SchedulerFactory
SchdulerFactory,有两个实现:DirectSchedulerFactory和 StdSchdulerFactory。前者可以用来在代码里定制你自己的Schduler参数。后者是直接读取classpath下的quartz.properties(不存在就都使用默认值)配置来实例化Schduler。通常来讲,我们使用StdSchdulerFactory也就足够了。

Scheduler

Scheduler
Scheduler就是Quartz的大脑,所有任务都是由它来控制。
Scheduler接口有三个实现类:

  • 1.StdScheduler
  • 2.RemoteScheduler
  • 3.RemoteMBeanScheduler(JBoss4RMIRemoteMBeanScheduler)

接口中定义了一些重要的方法,例如:

  • scheduleJob(JobDetail jobDetail, Trigger trigger) 将用户定义好的job和trigger进行绑定。
  • start() 开始调度任务
  • shutdown()/addJob(…)/deleteJobs/pauseJob/… 对任务的一些操作,可调度控制

关于调度的流程和细节,单独写了一篇进行介绍,见《Quartz Scheduler调度流程分析》

JobStore
JobStore是会来存储运行时信息的,包括Trigger,Schduler,JobDetail,业务锁等。它有多种实现RAMJobStore(内存实现),JobStoreTX(JDBC,事务由Quartz管理),JobStoreCMT(JDBC,使用容器事务),ClusteredJobStore(集群实现)。

RAMJobStore把数据存入内存,性能最高,配置也简单,但缺点是系统挂了难以恢复数据。JDBCJobStore保存数据到数据库,保证数据的可恢复性,但性能较差且配置复杂。

总结

Quart核心类图如下:
UML


参考: