1. 背景
在软件开发中经常会遇到使用任务调度的情况,比如需要定时,或者某个时刻执行某项任务。Quartz 是一个在java开中优秀的可选框架。
2.知识
什么是 Quartz 作业调度库?
Quartz 是一个Java下作业控制的开源框架。用来创建或简单或复杂的调度时间表,执行Java下任意数量的作业。
示例用途:
- 驱动流程工作流:比如下新订单时,安排一个作业在 2 小时内触发检查该订单的状态,如果未收到订单确认消息,将订单的状态更改为“等待干预”。
- 系统维护:安排一项作业,在每个工作日晚上 11:30 将数据库内容转储到 XML 文件中。
- 在应用程序中提供提醒服务。
特征
- 运行环境:Quartz 可以作为框集成到spring应用中,或者作为应用独立运行,或者在 servlet 容器中运行。
- 作业调度: 作业可被安排在特定触发器触发时运行,比如在一天中的某个时间,每周每月的特定日子,重复次数,无限重复等。
- 工作执行:写一个 实现 Job 接口的 Java 类即可。
- 持久化:可选择将任务存储在 JDBC数据源中,或者内存中。
- 监听器和插件:可监听捕获调度事件以监视或控制作业/触发器行为
- 支持事务,支持集群和故障转移
3. 快速示例
3.1 下载
在官网下载 jar 包 http://www.quartz-scheduler.org/downloads/ 或者使用 maven 集成:
(1) 添加依赖 依赖核心库和 logback 的日志。
代码语言:javascript复制 <!-- Quartz 核心库 -->
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
<!-- Quartz 使用了 SLF4J, 这里指定了一个实际使用的 logger -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
(2) 添加一个配置文件 在resources 文件下新建一个 quartz.properties 配置文件,这个配置文件不是必须的,不过仍然建议将配置内容放入到一个配置文件中,这样的配置文件比写在代码里更方便修改。
代码语言:javascript复制org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 5
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
(3) 编写一个 Job ,即作业类 一个 quertz 作业必须是实现了 Job 接口的实现类,实现 execute 方法,在 execute 方法的参数中可以获得一个 JobExecutionContext 上下文对象。 示例如下:
代码语言:javascript复制 public static class MyJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
String now = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
System.out.println("Run ..." now);
}
}
(4)开始执行作业 分几个初始化的步骤:
- 1、创建调度器 Scheduler 方法: Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
- 2、创建一个 JobDetail
- 3、创建一个 Trigger
- 4、将 作业 加入到调度器中: scheduler.scheduleJob(jobDetail, trigger);
说明:
- 创建 JobDetail 时构造方法传入上一步创建的 job 实现类,它表示一个可执行多次的作业,可以多种日程的方式来执行。
- Trigger 指定了触发执行的条件,可关联到一个日程计划对象。
- 最后使用调度器来启动
示例代码:
代码语言:javascript复制 public static void main(String[] args) throws InterruptedException {
try {
// 装载一个 调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
// 创建一个 job 作业
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1", "group1")
.build();
// 创建触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triger1", "group1")
.startNow()
.withSchedule(SimpleScheduleBuilder // 创建执行日程,这里是间隔4秒且始终循环
.simpleSchedule()
.withIntervalInSeconds(4)
.repeatForever())
.build();
// 将 作业 加入到调度器中
scheduler.scheduleJob(jobDetail, trigger);
} catch (SchedulerException e) {
e.printStackTrace();
}
// 为了让程序不终止
Thread.sleep(60000);
}
4. 使用 Quartz
Quartz API 的关键接口是:
-
Scheduler
调度器 - 调度程序的主要对象。 -
Job
作业 - 业务逻辑要实现的接口,你要执行的任务。 -
JobDetail
作业实例 - 用于定义作业的实例。它使用 JobBuilder 来创建 JobDetail 实例 -
Trigger
触发器 - 它定义了在某个时刻触发作业的方式。它使用 TriggerBuilder 构建 Trigger 实例。
主要示例:
代码语言:javascript复制 // define the job and tie it to our HelloJob class
JobDetail job = newJob(HelloJob.class)
.withIdentity("myJob", "group1") // name "myJob", group "group1"
.build();
// Trigger the job to run now, and then every 40 seconds
Trigger trigger = newTrigger()
.withIdentity("myTrigger", "group1")
.startNow()
.withSchedule(simpleSchedule()
.withIntervalInSeconds(40)
.repeatForever())
.build();
// Tell quartz to schedule the job using our trigger
sched.scheduleJob(job, trigger);
Quartz 附带了一些不同的触发器类型,最常用的是
- SimpleTrigger
- CronTrigger。
如果您需要在给定的时间只执行一次作业,或者需要在给定的时间触发作业,并让它重复 N 次,可选择 SimpleTrigger。
如果您希望基于类似日历的时间表进行触发,例如“每个星期五中午”或“每个月的第 10 天的 10:15”,可选择 CronTrigger 。
4.2 调度器 Scheduler
在使用 调度器(Scheduler),要先实例化一个 调度器,可使用 SchedulerFactory 来做。
要注意的是,调度器在实例化以后,要先启动调度器才能触发作业的执行,示例如下:
代码语言:javascript复制 SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
Scheduler sched = schedFact.getScheduler();
sched.start();
4.3 通过 Calendar 日历对象 排除某个日子
Quartz 的 Calendar 日历对象(注意不是 java.util.Calendar 对象)可以在触发器中被定义,它存在在调度程序中通过名称与触发器关联。
日历在从触发器中排除 某个时间段 很有用。例如,创建每个工作日的上午 9:30 触发的工作,然后排除所有法定假期日历。
为方便起见,Quartz 包含了 org.quartz.impl.HolidayCalendar 类。
- 1、先实例化一个 HolidayCalendar,使用它的 addExcludedDate(Date date) 方法,添加要排除的日期
- 2、然后 通过 addCalendar(..) 方法注册到调度程序中。
日历示例
代码语言:javascript复制HolidayCalendar cal = new HolidayCalendar();
cal.addExcludedDate( someDate );
cal.addExcludedDate( someOtherDate );
sched.addCalendar("myHolidays", cal, false);
Trigger t = newTrigger()
.withIdentity("myTrigger")
.forJob("myJob")
.withSchedule(dailyAtHourAndMinute(9, 30)) // execute job daily at 9:30
.modifiedByCalendar("myHolidays") // but not on holidays
.build();
// .. schedule job with trigger
Trigger t2 = newTrigger()
.withIdentity("myTrigger2")
.forJob("myJob2")
.withSchedule(dailyAtHourAndMinute(11, 30)) // execute job daily at 11:30
.modifiedByCalendar("myHolidays") // but not on holidays
.build();
4.4 简单触发器 SimpleTrigger
如果您需要在特定时刻执行一次作业,或者在特定时刻执行一次,然后以特定间隔重复执行,可选择 SimpleTrigger。
SimpleTrigger 的属性包括:
- 开始时间 start-time
- 结束时间 end-time
- 重复计数 repeat count
- 重复间隔 repeat interval
更多请阅读:https://github.com/quartz-scheduler/quartz/blob/master/docs/tutorials/tutorial-lesson-05.md
4.5 CronTrigger
使用 CronTrigger,您可以指定触发时间表,例如“每个星期五中午”或“每个工作日和上午 9:30”,甚至“每个星期一、星期三上午 9:00 到上午 10:00 之间每 5 分钟一次”和一月份的星期五”。
Cron 表达式 Cron-Expressions用于配置 CronTrigger 的实例。Cron-Expressions 是实际上由七个子表达式组成的字符串,它们描述了计划的各个细节。这些子表达式用空格分隔,表示:
- 秒
- 分钟
- 小时
- 每月的某天
- 月
- 星期几
- 年份(可选字段)
示例 Cron 表达式 CronTrigger 示例 1 - 创建触发器的表达式,该触发器每 5 分钟触发一次
“0 0/5 * * * ?”
CronTrigger 示例 2 - 创建触发器的表达式,该触发器每 5 分钟触发一次,每分钟后 10 秒(即上午 10:00:10、上午 10:05:10 等)。
“10 0/5 * * * ?”
CronTrigger 示例 3 - 创建触发器的表达式,该触发器在每周三和周五的 10:30、11:30、12:30 和 13:30 触发。
“0 30 10-13 ?*周三,周五”
构建一个触发器,它会在每天早上 8 点到下午 5 点之间每隔一分钟触发一次:
构建 CronTriggers 的示例
代码语言:javascript复制 trigger = newTrigger()
.withIdentity("trigger3", "group1")
.withSchedule(cronSchedule("0 0/2 8-17 * * ?"))
.forJob("myJob", "group1")
.build();
4.6 监听器
侦听器是您创建的对象,用于根据调度程序中发生的事件执行操作。
大多数情况不使用侦听器,但在需要事件通知
时很方便。
触发器监听器( TriggerListeners) TriggerListeners接收与触发器相关的事件
作业监听器(JobListeners) JobListeners 接收与作业相关的事件。
调度程序监听器(SchedulerListeners) 用于接收 Scheduler 本身内的事件通知。
4.7 作业的持久化存储方式
内存存储库(RAMJobStore) RAMJobStore 是使用最简单的 JobStore,它也是性能最高的。RAMJobStore 以显而易见的方式得名:它将所有数据保存在 RAM 中。缺点是当您的应用程序结束(或崩溃)时,所有调度信息都将丢失。
配置 Quartz 以使用 RAMJobStore:
代码语言:javascript复制org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
JDBC作业存储 (JDBCJobStore) 它通过 JDBC 将所有数据保存在数据库中,检索和更新触发触发器的时间通常少于 10 毫秒。
要使用 JDBCJobStore,您必须首先创建一组数据库表,docs/dbTables”目录中找到创建表的 SQL 脚本,需要注意的一件事是,在这些脚本中,所有表都以前缀“QRTZ_”开头。
代码语言:javascript复制org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.tablePrefix = QRTZ_
如果您的调度程序很忙, 几乎总是执行与线程池大小相同数量的作业,那么您应该将 DataSource 中的连接数设置为线程池大小 2。
5. 扩展
集群 集群目前适用于 JDBC-Jobstore(JobStoreTX 或 JobStoreCMT)和 TerracottaJobStore。功能包括负载平衡和作业故障转移(如果 JobDetail 的“请求恢复”标志设置为 true)。
使用 JobStoreTX 或 JobStoreCMT 进行聚类 通过将“org.quartz.jobStore.isClustered”属性设置为“true”来启用集群。
集群中的每个实例都应该使用quartz.properties 文件的相同副本。
例外情况是使用相同的属性文件,集群中的每个节点必须有一个唯一的 instanceId,通过将“AUTO”作为该属性的值可以完成(不需要不同的属性文件)。
6.参考:
官方网址: http://www.quartz-scheduler.org/ Github: https://github.com/quartz-scheduler/quartz/tree/master/docs/tutorials 官方训练课程 https://github.com/quartz-scheduler/quartz/tree/master/docs/tutorials