天天看点

Quartz 基本概念

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/catoop/article/details/70246683

核心概念

1.Job:Job是任务执行的流程,是一个类

2.JobDetail:JobDetail是Job是实例,是一个对象,包含了该实例的执行计划和所需要的数据

3.Trigger:Trigger是定时器,决定任务何时执行

4.Scheduler:调度器,调度器接受一组JobDetail+Trigger即可安排一个任务,其中一个JobDetail可以关联多个Trigger

实例

1.初始化:

  1. Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

  2. scheduler.start();

当程序退出时,应该主动调用shutdown()方法关闭该调度器。

2.Job:

一个Job类需要实现org.quartz.Job接口,这个接口值需要实现一个方法即可:

  1. void execute(JobExecutionContext context) throws JobExecutionException

context是重要的上下文,可以访问到关联的JobDetail对象和本次触发的Trigger对象,以及在此之上设定的数据。

3.JobDetail:

可以使用JobBuilder来构建一个JobDetail对象:

  1. JobDetail job = JobBuilder.newJob(MyJob.class) // MyJob是我实现的Job类

  2. .withIdentity("myjob") // 可以给该JobDetail起一个id,便于之后的检索

    。也可以 .withIdentity("myjob", "group1")
  3. .requestRecovery() // 执行中应用发生故障,需要重新执行

  4. .storeDurably() // 即使没有Trigger关联时,也不需要删除该JobDetail

  5. .usingJobData("key1", "value1")

  6. .usingJobData("key2", "value2") // 以Key-Value形式关联数据

  7. .build();

Quartz因为考虑到有些任务不是幂等的,不可以多次重复执行,所以默认没有开启“requestRecovery”。当确认业务中允许一次任务执行两次的情况下,可以开启该选项,则任务肯定不会因为应用停止而漏调用,但缺点就是,有可能会重复调用。

每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中,可以通过context取出该数据,进行业务流程处理。

4.Trigger:

可以使用TriggerBuilder来构建一个Trigger对象:

  1. Trigger trigger = TriggerBuilder.newTrigger()

  2. .forJob("myjob") // 关联上述的JobDetail

  3. .withIdentity("myjob-trigger1") // 给该Trigger起一个id

  4. .startAt(DateBuilder.futureDate(20, IntervalUnit.SECOND)) // 延迟20秒开始

  5. .withSchedule(SimpleScheduleBuilder.repeatMinutelyForever()) // 每分钟触发一次,无限循环

  6. .usingJobData("key3", "value3")

  7. .usingJobData("key4", "value4") // 以Key-Value形式关联数据

  8. .build();

5.设定:

因为上述的Trigger已经关联了JobDetail,可以使用

  1. scheduler.scheduleJob(trigger);

把这一组JobDetail和Trigger加载到调度器上,接下来就会按照计划执行Job任务。

6.配置文件:

配置文件不是必须的,Quartz对配置项都是有默认值的,当需要自定义的时候,可以在classpath路径下放一个quartz.properties文件,Quartz的StdSchedulerFactory在启动时会自动加载该配置文件。

比较值得关注的是这两个配置项:

  1. org.quartz.threadPool.threadCount=50

  2. org.quartz.scheduler.batchTriggerAcquisitionMaxCount=50

第一个配置项是线程池里的线程数,默认值是10,当执行任务会并发执行多个耗时任务时,要根据业务特点选择线程池的大小。

第二个配置是,当检查某个Trigger应该触发时,默认每次只Acquire一个Trigger,(为什么要有Acquire的过程呢?是为了防止多线程访问的情况下,同一个Trigger被不同的线程多次触发)。尤其是使用JDBC JobStore时,一次Acquire就是一个update语句,尽可能一次性的多获取几个Trigger,一起触发,当定时器数量非常大的时候,这是个非常有效的优化。当定时器数量比较少时,触发不是极为频繁时,这个优化的意义就不大了。

6.持久化

如果定时器在业务中属于关键数据,需要在故障重启后恢复状态,则需要把Quartz配置为持久化模式。默认情况下,所有定时任务和数据都保存在内存中,在应用重启后状态会消失。

JobStore决定了Quartz如何存储任务和触发器,默认值是org.quartz.simpl.RAMJobStore,我们需要把它配置为org.quartz.impl.jdbcjobstore.JobStoreTX,即可以使用JDBC数据源,把状态持久化到关系型数据库中。

用H2数据库进行举例,配置文件如下:

  1. org.quartz.dataSource.DATA_SOURCE_NAME.driver=org.h2.Driver

  2. org.quartz.dataSource.DATA_SOURCE_NAME.URL=jdbc:h2:quartz;MVCC=TRUE

  3. org.quartz.dataSource.DATA_SOURCE_NAME.user=sa

  4. org.quartz.dataSource.DATA_SOURCE_NAME.password=

  5. org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX

  6. org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate

  7. org.quartz.jobStore.dataSource=DATA_SOURCE_NAME

  8. # org.quartz.jobStore.useDBLocks=true

Quartz为了保证多线程下的一致性,使用了锁,但是使用了使用Lock表里的一行记录来模拟锁的形式来实现的,其实性能很糟糕,还不如使用默认的基于Java的Monitor锁的效果好,所以我注释掉了这个配置项。

之后,打开quartz-x.x.x.tar.gz包内的docs/dbTables文件夹内有各种数据库下的建表语句,直接在数据库中执行,把表先建好。

配置文件中增加上述对JobStore的设定后,代码不用修改一行,所有的任务和触发器已经是持久化的了,当应用停机重启后,错过的(misfire)任务和上次正在执行的(recovery)任务都会回复状态,如果JobDetail指定了requestRecovery,上次执行中,但没有执行完毕的任务会重新执行一遍。

注意事项:

> 一个任务JOB可以添加多个Trigger 但是一个Trigger只能绑定一个JOB 这点需要注意。

> 在用JobBuilder创建JobDetail的时候,有一个storeDurably()方法,可以在没有触发器指向任务的时候,使用 sched.addJob(job, true) 将任务保存在队列中了。而后使用 sched.scheduleJob 触发。如果不使用 storeDurably ,则在添加 Job 到引擎的时候会抛异常,意思就是该 Job 没有对应的 Trigger。

> 要求cluster上所有的node的时间应该是一样的。