Quartz是Java中一个简单实用的任务调度框架。相比于JDK自带的TimerTask类Quartz的功能更为强大。Quartz支持Cron表达式,能够实现定时任务调度信息持久化到数据库,支持集群模式等。
Github主页:https://github.com/quartz-scheduler/quartz
单独使用Quartz框架需要引入如下依赖:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
Quartz使用SLF4J日志API,因此我们还需要在工程中引入SLF4J及其实现库,这里使用LogBack:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-access</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
在正式使用Quartz前我们需要了解框架中的几个基本概念。
Job:任务,就是我们具体要执行的功能,任务中包含具体的业务代码。
Trigger:触发器,指定调度任务的规则,例如特定时间点执行、固定时间间隔执行等。
Scheduler:调度器,负责具体读取触发器规则、调用任务、存取任务执行状态的组件。
Quartz框架就是围绕这三个核心概念构建的。
下面例子中,我们创建了一个任务,然后每1秒执行一次。
DemoJob.java
package com.gacfox.demoquartz;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DemoJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("任务执行 " + sdf.format(date));
}
}
Main.java
package com.gacfox.demoquartz;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class Main {
public static void main(String[] args) {
try {
// 获取调度器
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 创建触发器,每1秒执行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("jobDetail", "demoGroup")
// 这里设置触发器具体规则
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
// 查询Job在JobStore中是否存在,存在则直接启动,不存在先创建
JobDetail jobDetail = scheduler.getJobDetail(new JobKey("jobDetail", "demoGroup"));
if (jobDetail == null) {
// 创建任务实例
jobDetail = JobBuilder
.newJob(DemoJob.class)
// 这里设置任务名和任务组名,用于和触发器关联
.withIdentity("jobDetail", "demoGroup")
.build();
// 为调度器设置任务和触发器
scheduler.scheduleJob(jobDetail, trigger);
}
// 启动调度器
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
// 出于演示方便,这里直接死循环阻塞主线程,防止程序退出
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
quartz.properties
# 调度器实例名
org.quartz.scheduler.instanceName=QuartzScheduler
# 调度器实例ID 一般都为自动生成
org.quartz.scheduler.instanceId=AUTO
# 线程数
org.quartz.threadPool.threadCount=10
# 线程优先级 默认5即可
org.quartz.threadPool.threadPriority=5
# 线程池实现类
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# 使用基于内存的JobStore持久化调度信息
org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
我们的DemoJob
类中包含了定时执行的具体业务代码,该类实现了Job
接口。具体创建任务时,我们还需要通过Quartz提供的JobDetail
进行封装,指定任务名和任务组名提供给调度器。
触发器创建时,也需要指定任务名和任务组名,通过名字和任务关联,此外触发器还需要指定一个ScheduleBuilder
对象,它用于指定触发器的具体规则,这里我们使用的是SimpleScheduleBuilder
,通过几个构建器函数指定了任务触发规则为每间隔1秒执行一次,永远执行。
最后,我们将任务和触发器添加到调度器并启动即可。
quartz.properties
是Quartz框架的配置文件,内容主要包括调度器实例名、线程池配置、调度信息存储等内容,由于非常简单这里就不多介绍了,具体可以参考文档。
上面例子中我们使用了SimpleScheduleBuilder
来指定触发器规则,实际上通过Cron表达式触发也很常用。Cron表达式通用型好易于配置,而且更加灵活、简洁。
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("jobDetail", "demoGroup")
.withSchedule(CronScheduleBuilder.cronSchedule("0/1 * * * * ?"))
.build();
上面代码中,我们使用CronScheduleBuilder
创建了一个触发器规则,效果与上面完全相同。
注意:Quartz中的Cron表达式和Linux下语法有区别,最主要的区别是Linux下不支持秒级控制因此表达式少一位,我们使用时要注意区分。
Quartz的调度信息存储在JobStore中,默认情况下这些信息都存在于内存(RAMJobStore)。实际上,更通用的方式是使用数据库表存储调度信息,使得Quartz具备调度信息持久化、支持集群等。配置Quartz框架使用数据库存储信息前,我们需要先创建数据库表,相关SQL文件可以在官方仓库中找到。例如我们使用MySQL数据库,那么执行tables_mysql_innodb.sql
。
执行后会创建如下11张表,当然其内容都非常简单易懂,大家自己去看表的字段即可。
mysql> show tables;
+--------------------------+
| Tables_in_demoquartz |
+--------------------------+
| QRTZ_BLOB_TRIGGERS |
| QRTZ_CALENDARS |
| QRTZ_CRON_TRIGGERS |
| QRTZ_FIRED_TRIGGERS |
| QRTZ_JOB_DETAILS |
| QRTZ_LOCKS |
| QRTZ_PAUSED_TRIGGER_GRPS |
| QRTZ_SCHEDULER_STATE |
| QRTZ_SIMPLE_TRIGGERS |
| QRTZ_SIMPROP_TRIGGERS |
| QRTZ_TRIGGERS |
+--------------------------+
11 rows in set (0.00 sec)
注:这里注意Quartz使用的表名、字段名等均使用大写下划线的形式,这可能不符合很多团队的开发规范,但这确实是无法更改的。
此外为了连接MySQL数据库,我们还需要在工程中引入对应的MySQL驱动。
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
下面配置指定了Quartz框架使用MySQL数据库的相关配置。
# 调度器实例名
org.quartz.scheduler.instanceName=QuartzScheduler
# 调度器实例ID 一般都为自动生成
org.quartz.scheduler.instanceId=AUTO
# 线程数
org.quartz.threadPool.threadCount=10
# 线程优先级 默认5即可
org.quartz.threadPool.threadPriority=5
# 线程池实现类
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# 指定使用JDBCJobStore
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
# 开启集群模式
org.quartz.jobStore.isClustered=true
# 代理类用于指定数据库方言
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
# 数据源名
org.quartz.jobStore.dataSource=quartzDataSource
# 表前缀
org.quartz.jobStore.tablePrefix=QRTZ_
# 数据库驱动类
org.quartz.dataSource.quartzDataSource.driver=com.mysql.cj.jdbc.Driver
# 数据库链接
org.quartz.dataSource.quartzDataSource.URL=jdbc:mysql://127.0.0.1/demoquartz?useUnicode=true&characterEncoding=utf-8
# 数据库用户名
org.quartz.dataSource.quartzDataSource.user=root
# 数据库密码
org.quartz.dataSource.quartzDataSource.password=root
配置中我们指定了调度器的信息,此外我们还设置了org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
指定使用JDBC持久化调度信息,并指定org.quartz.jobStore.isClustered=true
开启集群模式,此时如果同一个服务有多个副本运行,Quartz就会通过基于数据库的分布式锁来进行分布式调度。我们可以尝试启动多个程序副本查看任务调度情况。
对于数据源,默认情况下Quartz使用单独的c3p0
连接池连接数据库,我们也可以手动指定其它数据源实例,具体可以参考文档进行相关配置。