版权声明:本文为博主原创文章,未经博主允许不得转载。 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.初始化:
-
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
-
scheduler.start();
当程序退出时,应该主动调用shutdown()方法关闭该调度器。
2.Job:
一个Job类需要实现org.quartz.Job接口,这个接口值需要实现一个方法即可:
-
void execute(JobExecutionContext context) throws JobExecutionException
context是重要的上下文,可以访问到关联的JobDetail对象和本次触发的Trigger对象,以及在此之上设定的数据。
3.JobDetail:
可以使用JobBuilder来构建一个JobDetail对象:
-
JobDetail job = JobBuilder.newJob(MyJob.class) // MyJob是我实现的Job类
-
。也可以 .withIdentity("myjob", "group1").withIdentity("myjob") // 可以给该JobDetail起一个id,便于之后的检索
-
.requestRecovery() // 执行中应用发生故障,需要重新执行
-
.storeDurably() // 即使没有Trigger关联时,也不需要删除该JobDetail
-
.usingJobData("key1", "value1")
-
.usingJobData("key2", "value2") // 以Key-Value形式关联数据
-
.build();
Quartz因为考虑到有些任务不是幂等的,不可以多次重复执行,所以默认没有开启“requestRecovery”。当确认业务中允许一次任务执行两次的情况下,可以开启该选项,则任务肯定不会因为应用停止而漏调用,但缺点就是,有可能会重复调用。
每个JobDetail内都有一个Map,包含了关联到这个Job的数据,在Job类中,可以通过context取出该数据,进行业务流程处理。
4.Trigger:
可以使用TriggerBuilder来构建一个Trigger对象:
-
Trigger trigger = TriggerBuilder.newTrigger()
-
.forJob("myjob") // 关联上述的JobDetail
-
.withIdentity("myjob-trigger1") // 给该Trigger起一个id
-
.startAt(DateBuilder.futureDate(20, IntervalUnit.SECOND)) // 延迟20秒开始
-
.withSchedule(SimpleScheduleBuilder.repeatMinutelyForever()) // 每分钟触发一次,无限循环
-
.usingJobData("key3", "value3")
-
.usingJobData("key4", "value4") // 以Key-Value形式关联数据
-
.build();
5.设定:
因为上述的Trigger已经关联了JobDetail,可以使用
-
scheduler.scheduleJob(trigger);
把这一组JobDetail和Trigger加载到调度器上,接下来就会按照计划执行Job任务。
6.配置文件:
配置文件不是必须的,Quartz对配置项都是有默认值的,当需要自定义的时候,可以在classpath路径下放一个quartz.properties文件,Quartz的StdSchedulerFactory在启动时会自动加载该配置文件。
比较值得关注的是这两个配置项:
-
org.quartz.threadPool.threadCount=50
-
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数据库进行举例,配置文件如下:
-
org.quartz.dataSource.DATA_SOURCE_NAME.driver=org.h2.Driver
-
org.quartz.dataSource.DATA_SOURCE_NAME.URL=jdbc:h2:quartz;MVCC=TRUE
-
org.quartz.dataSource.DATA_SOURCE_NAME.user=sa
-
org.quartz.dataSource.DATA_SOURCE_NAME.password=
-
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
-
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-
org.quartz.jobStore.dataSource=DATA_SOURCE_NAME
-
# 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的时间应该是一样的。