写点什么

任务调度框架 Quartz 快速入门

发布于: 2020 年 12 月 28 日
任务调度框架Quartz快速入门

Quartz 是什么

Quartz 是一个功能强大的开源任务调度库,几乎可以集成到任何 Java 应用程序中,无论是超小型的独立应用还是超大型电子商务系统。

它常用于企业级应用中:

  • Driving Process Workflow:当新订单下达,可以安排一个 30 分钟内触发的任务,检查订单状态。

  • System Maintenance:安排每个工作日晚上 11 点将数据库内容转储到文件的任务。

  • Providing reminder services:提供提醒服务。

Quartz 还支持集群模式和对 JTA 服务。

Quartz 中的重要 API 及概念

http://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/

超重要 API

  • Scheduler 和调度程序交互的主要 API 生命周期从 SchedulerFactoru 创建它开始,到调用 shutdown 方法结束。一旦 Scheduler 创建,任何关于 scheduling 相关的事,他都为所欲为:添加、删除、列出所有的 Jobs 和 triggers、暂停触发器等。在 start 方法之前,不会做任何事情。

  • Job 你希望被调度器调度的任务组件接口。当 Job 的触发器触发时,调度程序的工作线程将调用 execute 方法。该方法接收一个 JobExecutionContext 对象,为 Job 实例提供了丰富的运行时环境信息,比如:scheduler、trigger、jobDataMap、job、calendar、各种 time 等。

  • JobDetail 用于定义任务。JobDetail 对象由 Quartz 客户端在将 job 加入 Scheduler 提供,也就是你的程序。它包含了不同为 job 设置的属性,还有可以用来为 job 储存状态信息的 JobDataMap。注意它和 Job 的区别,它实际上是 Job 实例的属性。【Job 定义如何执行,JobDetail 定义有何属性】

  • Trigger 触发任务执行。触发器可能具有与之关联的 JobDataMap,以便于将特定于触发器触发的参数传递给 Job。Quartz 提供了几种不同的触发器,SimpleTrigger 和 CronTrigger 比较常用。如果你需要一次性执行作业或需要在给定的时间触发一个作业并重复执行 N 次且有两次执行间有延时 delay,SimpleTrigger 较为方便。如果你希望基于类似日期表触发执行任务,CronTrgger 推荐使用。

  • JobBuilder 用于构建 JobDetail 的。

  • TriggerBuilder 用于构建 Trigger 的。

Quartz 提供了各种各样的 Builder 类,定义了 Domain Specific Language,且都提供了静态的创建方法,我们可以使用 import static 简化书写。

重要概念

  • Identity 当作业和触发器在 Quartz 调度程序中注册时,会获得标识键。JobKey 和 TriggerKey 允许被置入 group 中,易于组织管理。唯一的,是 name 和 group 的组合标识。

  • JobDataMap 是 Map 的实现,具有 key-value 相关操作,存储可序列化数据对象,供 Job 实例在执行时使用。可以使用 usingJobData(key,value)在构建 Jobdetail 的时候传入数据,使用 jobDetail.getJobDataMap()获取 map。

Quartz 设计理念:为什么设计 Job 和 Trigger?

While developing Quartz, we decided that it made sense to create a separation between the schedule and the work to be performed on that schedule. This has (in our opinion) many benefits. For example, Jobs can be created and stored in the job scheduler independent of a trigger, and many triggers can be associated with the same job. Another benefit of this loose-coupling is the ability to configure jobs that remain in the scheduler after their associated triggers have expired, so that that it can be rescheduled later, without having to re-define it. It also allows you to modify or replace a trigger without having to re-define its associated job.

隔离 schedule 和 schedule 上执行的 Job,优点是可见的:

可以独立于触发器创建作业并将其存储在作业调度程序中,并且许多触发器可以与同一作业相关联。这样的松耦合好处是什么?

  • 如果触发器过期,作业还可以保留在程序中,以便重新调度,而不必重新定义。

  • 如果你希望修改替换某个触发器,你不必重新定义其关联的作业。

最简单的 Quartz 使用案例

导入依赖

<dependency>    <groupId>org.quartz-scheduler</groupId>    <artifactId>quartz</artifactId>    <version>2.3.2</version></dependency>

复制代码

简单案例如下

public class QuartzTest {	// 你需要在start和shutdown之间执行你的任务。    public static void main(String[] args) {
try { // 从工厂中获取Scheduler示例 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 开始 scheduler.start();
// 定义Job,并将其绑定到HelloJob类中 JobDetail job = JobBuilder.newJob(HelloJob.class) .withIdentity("job1", "group1") // name 和 group .usingJobData("username", "天乔巴夏") // 置入JobDataMap .usingJobData("age", "20") .withDescription("desc-demo") .build();
// 触发Job执行,每40s执行一次 Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1", "group1") .startNow() // 立即开始 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(40) .repeatForever()) .build();
// 告诉 quartz 使用trigger来调度job scheduler.scheduleJob(job, trigger); // 关闭,线程终止 scheduler.shutdown();
} catch (SchedulerException se) { se.printStackTrace(); } }
}
@Slf4j@NoArgsConstructorpublic class HelloJob implements Job {
@Override public void execute(JobExecutionContext context) throws JobExecutionException { // 从context中获取属性 JobDetail jobDetail = context.getJobDetail(); JobDataMap jobDataMap = jobDetail.getJobDataMap(); JobKey key = jobDetail.getKey(); Class<? extends Job> jobClass = jobDetail.getJobClass(); String description = jobDetail.getDescription();
String username = jobDataMap.getString("username"); int age = jobDataMap.getIntValue("age");
log.info("\nJobKey : {},\n JobClass : {},\n JobDesc : {},\n username : {},\n age : {}", key, jobClass.getName(), description, username, age); }}
复制代码

启动测试,打印日志如下:

01:23:12.406 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor01:23:12.414 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main01:23:12.430 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl01:23:12.430 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.01:23:12.432 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.01:23:12.433 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.  NOT STARTED.  Currently in standby mode.  Number of jobs executed: 0  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.
01:23:12.433 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'01:23:12.433 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.201:23:12.434 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.01:23:12.434 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers01:23:12.443 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers01:23:12.445 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.job1', class=com.hyhwky.HelloJob01:23:12.451 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers01:23:12.452 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.job101:23:12.452 [DefaultQuartzScheduler_Worker-1] INFO com.hyhwky.HelloJob - JobKey : group1.job1, JobClass : com.hyhwky.HelloJob, JobDesc : desc-demo, username : 天乔巴夏, age : 20
复制代码

我们可以看到 quartz 已经被初始化了,初始化配置如下,在 org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar!\org\quartz\quartz.properties

# 调度器配置org.quartz.scheduler.instanceName: DefaultQuartzSchedulerorg.quartz.scheduler.rmi.export: falseorg.quartz.scheduler.rmi.proxy: falseorg.quartz.scheduler.wrapJobExecutionInUserTransaction: false
# 线程池配置org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10org.quartz.threadPool.threadPriority: 5org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
# 存储配置org.quartz.jobStore.misfireThreshold: 60000 #trigger 容忍时间60s
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore
复制代码

更多的配置:Quartz Configuration Reference

Job 实例和 JobDetail

Job 和 JobDetail

  • Job 是正在执行的作业实例,JobDetail 是作业定义。

  • 一个 Job 可以创建多个 JobDetail,拥有不同的 JobDataMap。

这样定义有什么好处呢?举个例子吧,假设现在你定义了一个类实现了 Job 接口,比如:SendEmailJob。如果你希望根据用户的姓名,选择指定的人发送,那么你可以通过 JobDataMap 绑定参数传递进 JobDetail 中,也就是说我们需要创建两个不同的 JobDetail,比如:SendEmailToSummerday 和 SendEmailToTQBX,他们拥有各自独立的 JobDataMap,实现更加灵活。

Job 的 State 和 Concurrency

https://blog.csdn.net/fly_captain/article/details/83029440

@DisallowConcurrentExecution

该注解标注在 Job 类上,意思是不能并发从同一个 JobDetail 定义的多个实例,但可以同时执行多个不同的 JobDetail 定义的实例。

拿上面的例子继续举例,假设 SendEmailJob 标注了此注解,表明同一时间可以同时执行 SendEmailToSummerday 和 SendEmailToTQBX,因为他们是不同的 JobDetail,但是不能同时执行多个 SendEmailToSummerday。

@PersistJobDataAfterExecution

该注解也标注在 Job 类上,告诉 Scheduler 正常执行完 Job 之后,重新存储更新一下 JobDataMap。一般标注此注解的 Job 类应当考虑也加上 @DisallowConcurrentExecution 注解,以避免同时执行 Job 时出现 JobDataMap 存储的竞争。

Trigger 常见使用

更多例子可以查看文档,非常详细:SimpleTrigger 和 CronTrigger

构建一个触发器,该触发器将立即触发,然后每五分钟重复一次,直到小时 22:00:

import static org.quartz.TriggerBuilder.*;import static org.quartz.SimpleScheduleBuilder.*;import static org.quartz.DateBuilder.*: 
SimpleTrigger trigger = (SimpleTrigger) newTrigger() .withIdentity("trigger7", "group1") .withSchedule(simpleSchedule() .withIntervalInMinutes(5) .repeatForever()) .endAt(dateOf(22, 0, 0)) .build();
复制代码

构建一个触发器,该触发器将在周三上午 10:42 触发,在系统默认值以外的时区中:

import static org.quartz.TriggerBuilder.*;import static org.quartz.CronScheduleBuilder.*;import static org.quartz.DateBuilder.*:
trigger = newTrigger() .withIdentity("trigger3", "group1") .withSchedule(cronSchedule("0 42 10 ? * WED")) // [秒] [分] [时] [月的第几天] [月] [一星期的第几天] [年(可选)] .inTimeZone(TimeZone.getTimeZone("America/Los_Angeles")) .forJob(myJobKey) .build();
复制代码

Quartz 存储与持久化

Job 的持久化是非常重要的,如果 Job 不能持久化,一旦不再有与之关联的 Trigger,他就会自动从调度程序中被删除。

小结

Job 存储器的类型:


具体使用方法:http://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/tutorial-lesson-09.html

以上就是要与大家分享的内容了

原文链接:https://www.cnblogs.com/summerday152/p/14192845.html

如果觉得本文对你有帮助,可以关注一下我公众号,回复关键字【面试】即可得到一份 Java 核心知识点整理与一份面试大礼包!另有更多技术干货文章以及相关资料共享,大家一起学习进步!


发布于: 2020 年 12 月 28 日阅读数: 575
用户头像

领取资料添加小助理vx:bjmsb2020 2020.12.19 加入

Java领域;架构知识;面试心得;互联网行业最新资讯

评论

发布
暂无评论
任务调度框架Quartz快速入门