作业调度框架 Quartz 的基本使用

Quartz 介绍

Quartz 是一个开源的作业调度框架,可以让计划的程序任务一个预定义的日期和时间运行。

Quartz 可以用来创建简单或复杂的日程安排执行几十,几百,甚至是十万的作业数。

Quartz 官网:http://www.quartz-scheduler.org/

GitHub:https://github.com/quartz-scheduler

Quartz 功能

如果应用程序需要在给定时间执行任务,或者如果系统有连续维护作业,那么 Quartz 是理想的解决方案。

使用 Quartz 作业调度应用的示例:

  • 驱动处理工作流程:作为一个新的订单被初始化放置,调度作业到在正好两个小时内,它将检查订单的状态,如果订单确认消息尚未收到命令触发警告通知,以及改变订单的状态为 “等待的干预”。
  • 系统维护:调度工作给数据库的内容,每个工作日(节假日除外平日)在 11:30 PM 转储到一个 XML 文件中。
  • 在应用程序内提供提醒服务

运行环境

  • Quartz 可以运行嵌入在另一个独立式应用程序
  • Quartz 可以在应用程序服务器 (或 servlet 容器) 内被实例化,并且参与 XA 事务
  • Quartz 可以作为一个独立的程序运行 (其自己的 Java 虚拟机内),可以通过 RMI 使用
  • Quartz 可以被实例化,作为独立的项目集群 (负载平衡和故障转移功能),用于作业的执行

作业调度

作业被安排在一个给定的触发时运行。触发器可以使用以下指令的接近任何组合来创建:

  • 在一天中的某个时间(到毫秒)
  • 在一周的某几天
  • 在每月的某一天
  • 在一年中的某些日期
  • 不在注册的日历中列出的特定日期(如商业节假日除外)
  • 重复特定次数
  • 重复进行,直到一个特定的时间 / 日期
  • 无限重复
  • 重复的延迟时间间隔

作业是由其创建者赋予的名字,也可以组织成命名组。触发器也可以给予名称和放置在组中,以方便地将它们调度内组织。

作业可以被添加到所述调度器一次,而是具有多个触发器注册。

在企业 Java 环境中,作业可以执行自己的工作作为分布式(XA)事务的一部分。

作业执行

  • 作业可以实现简单的作业接口,为作业执行工作的任何 Java 类。
  • Job 类的实例可以通过 Quartz 被实例化,或者通过应用程序框架。
  • 当触发时,调度通知实现 JobListener 和 TriggerListener 接口零个或多个 Java 对象(监听器可以是简单的 Java 对象,或 EJB,JMS 或发布者等)。这些监听器在作业已经执行之后通知。
  • 由于作业完成后返回 JobCompletionCode,它通知的成功或失败的调度。JobCompletionCode 还可以指示的基础上,成功的话就采取行动调度 / 失败的代码 - 如立即重新执行作业。

作业持久性

  • Quartz 的设计包括可被实现以提供的作业存储各种机制一个作业存储接口
  • 通过使用包含的 JDBCJobStore,所有的作业和触发器配置为 “非挥发性” 都存储在通过 JDBC 关系数据库。
  • 通过使用包含的 RAMJobStore,所有的作业和触发器存储在 RAM,因此不计划执行仍然存在 - 但这是无需使用外部数据库的优势。

事务

  • 可以参与 JTA 事务,通过使用 JobStoreCMT(JDBCJobStore 的子类)。
  • Quartz 可以管理 JTA 事务(开始并提交它们)周围作业的执行,从而使作业执行的工作自动将 JTA 事务中发生。

集群

  • 故障切换
  • 负载均衡
  • Quartz 的内置的群集功能,通过 JDBCJobStore(如上所述)依靠数据库持久
  • Terracotta 扩展 Quartz 提供集群功能,而不需要一个支持数据库

监听器和插件

  • 应用程序可以捕捉事件的调度监控或通过实现一个或多个监听器接口控制工作 / 触发行为。
  • 插件机制,可以用来添加功能,Quartz 让作业执行过程中或工作负载和触发定义的历史不受限在一个文件中。
  • 附带了一些 “工厂建有” 插件和监听器。

Quartz 引入

<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz -->
<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

Quartz 简单示例

Quartz 作业

public class HelloJob implements Job {
	public void execute(JobExecutionContext context) throws JobExecutionException {
		System.out.println("Hello Quartz!");
	}
}

Quartz 触发器

定义 Quartz 触发器,运行在上面的 Quartz 作业。

Quartz 有两种类型的触发器在 Quartz2:

  • SimpleTrigger – 允许设置开始时间,结束时间,重复间隔。

    Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("dummyTriggerName", "group1")
        .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                   .withIntervalInSeconds(5).repeatForever())
        .build();
  • CronTrigger – 允许 UNIX cron 表达式来指定日期和时间来运行作业。

    Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("dummyTriggerName", "group1")
    	.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
        .build();

Scheduler

调度类链接 “工作” 和 “触发器” 到一起,并执行它。

Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
scheduler.scheduleJob(job, trigger);

完整示例

JobDetail job = JobBuilder.newJob(HelloJob.class).withIdentity("dummyJobName", "group1").build();

Trigger trigger = TriggerBuilder.newTrigger()
    .withIdentity("dummyTriggerName", "group1")
	.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?"))
    .build();

Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
scheduler.scheduleJob(job, trigger);

Quartz 作业监听

JobListener

创建一个 JobListener,只是实现了 JobListener 接口,并覆盖所有的接口的方法。

public class HelloJobListener implements JobListener {...}

加入监听

Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.getListenerManager()
	     .addJobListener(new HelloJobListener(), KeyMatcher.keyEquals(jobKey));

Quartz 执行多作业

在 Quartz 调度框架中,每个作业将被连接到一个唯一的触发,并且由调度器运行它。

P.S:在 Quartz 中,一个触发器触发多个作业是不可以的。

使用 QuartzAPI 声明上述 3 个作业,分配它们到特定触发器并调度它。

JobKey jobKeyA = new JobKey("jobA", "group1");
JobDetail jobA = JobBuilder.newJob(JobA.class).withIdentity(jobKeyA).build();

JobKey jobKeyB = new JobKey("jobB", "group1");
JobDetail jobB = JobBuilder.newJob(JobB.class).withIdentity(jobKeyB).build();

JobKey jobKeyC = new JobKey("jobC", "group1");
JobDetail jobC = JobBuilder.newJob(JobC.class).withIdentity(jobKeyC).build();

Trigger trigger1 = TriggerBuilder.newTrigger()
    .withIdentity("dummyTriggerName1", "group1")
	.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build();

Trigger trigger2 = TriggerBuilder.newTrigger()
    .withIdentity("dummyTriggerName2", "group1")
	.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build();

Trigger trigger3 = TriggerBuilder.newTrigger()
    .withIdentity("dummyTriggerName3", "group1")
	.withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ?")).build();

Scheduler scheduler = new StdSchedulerFactory().getScheduler();

scheduler.start();
scheduler.scheduleJob(jobA, trigger1);
scheduler.scheduleJob(jobB, trigger2);
scheduler.scheduleJob(jobC, trigger3);

列出调度器所有作业

Scheduler scheduler = new StdSchedulerFactory().getScheduler();
for (String groupName : scheduler.getJobGroupNames()) {
	for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
		String jobName = jobKey.getName();
		String jobGroup = jobKey.getGroup();
		List<Trigger> triggers = (List<Trigger>) scheduler.getTriggersOfJob(jobKey);
		Date nextFireTime = triggers.get(0).getNextFireTime();
		System.out.println("[jobName] : " + jobName + " [groupName] : " + jobGroup + " - " + nextFireTime);
	}
}

参考资料

  • https://www.yiibai.com/quartz