Quartz简介和使用

Quartz是Java中一个简单实用的任务调度框架。相比于JDK自带的TimerTask类Quartz的功能更为强大。Quartz支持Cron表达式,能够实现定时任务调度信息持久化到数据库,支持集群模式等。

Github主页:https://github.com/quartz-scheduler/quartz

引入Maven依赖

单独使用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框架就是围绕这三个核心概念构建的。

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框架的配置文件,内容主要包括调度器实例名、线程池配置、调度信息存储等内容,由于非常简单这里就不多介绍了,具体可以参考文档。

触发器使用Cron表达式

上面例子中我们使用了SimpleScheduleBuilder来指定触发器规则,实际上通过Cron表达式触发也很常用。Cron表达式通用型好易于配置,而且更加灵活、简洁。

Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("jobDetail", "demoGroup")
        .withSchedule(CronScheduleBuilder.cronSchedule("0/1 * * * * ?"))
        .build();

上面代码中,我们使用CronScheduleBuilder创建了一个触发器规则,效果与上面完全相同。

注意:Quartz中的Cron表达式和Linux下语法有区别,最主要的区别是Linux下不支持秒级控制因此表达式少一位,我们使用时要注意区分。

持久化调度信息到MySQL数据库

Quartz的调度信息存储在JobStore中,默认情况下这些信息都存在于内存(RAMJobStore)。实际上,更通用的方式是使用数据库表存储调度信息,使得Quartz具备调度信息持久化、支持集群等。配置Quartz框架使用数据库存储信息前,我们需要先创建数据库表,相关SQL文件可以在官方仓库中找到。例如我们使用MySQL数据库,那么执行tables_mysql_innodb.sql

https://github.com/quartz-scheduler/quartz/blob/main/quartz/src/main/resources/org/quartz/impl/jdbcjobstore/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连接池连接数据库,我们也可以手动指定其它数据源实例,具体可以参考文档进行相关配置。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap