Quartz-scheduler 定时器概述、核心 API 与 快速入门

目录

quartz-scheduler 石英调度器概述

quartz-scheduler HelloWorld

quartz-scheduler 核心 API

quartz.properties Quartz Configuration

JobDataMap 任务数据对象

Job/JobDetail 实例 与 并发

MVC 访问调度 Quartz 任务


本文源码:https://github.com/wangmaoxiong/quartzapp

quartz-scheduler 石英调度器概述

1、Quartz 是功能强大的开源作业调度库,几乎可以集成到任何 Java 应用程序中,从最小的独立应用程序到最大的电子商务系统。Quartz 可用于创建简单或复杂的计划,以执行数以万计的工作;可以执行您编写的所有内容。

2、Quartz Scheduler 包含许多企业级功能,例如对 JTA 事务和集群的支持。Quartz 是免费使用的,并根据 Apache 2.0 许可获得许可。

Quartz 官网:http://www.quartz-scheduler.org/
二进制 jar 包下载地址:http://www.quartz-scheduler.org/downloads/
官网文档:http://www.quartz-scheduler.org/documentation/
github 开源地址:https://github.com/quartz-scheduler/quartz
w3c 教程:https://www.w3cschool.cn/quartz_doc/

3、Spring Boot 官方也对 Quartz 调度器进行了集成,Spring boot 官网文档:Quartz Scheduler

4、Java JDK 有原生的 计时器 Timer  以及 定时执行服务 ScheduledExecutorService ,Spring 也提供了 @Scheduled 执行定时任务。

5、生产中如果定时任务多,处理频繁,则强烈建议使用第三方封装的调度框架,因为定时器操作底层都是多线程的操作,任务的启动、暂停、恢复、删除、实质是线程的启动、暂停、中断、唤醒等操作。

quartz-scheduler HelloWorld

1、本文环境 Spring Boot 2.1.3 + java jdk 1.8 ,pom.xml 文件中导入 quartz 依赖

    <!--quartz 定时器 https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
        <version>2.2.5.RELEASE</version>
    </dependency>

源码:https://github.com/wangmaoxiong/quartzapp/blob/master/pom.xml

spring-boot-starter-quartz 组件内部依赖了如下的组件:

ategory/License Group / Artifact Version
Job Scheduling/Apache 2.0 org.quartz-scheduler » quartz 2.3.2
Apache 2.0 org.springframework » spring-context-support 5.2.4.RELEASE
Transactions/Apache 2.0 org.springframework » spring-tx 5.2.4.RELEASE
Apache 2.0 org.springframework.boot » spring-boot-starter 2.2.5.RELEASE

2、由浅入深,下面先以一个简单 main 方法调用来介绍 quartz 作业调度器的基本使用步骤。

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 自定义作业实现 org.quartz.Job 接口,execute 方法中写作业逻辑.
 */
public class HelloJob implements Job {
    private static Logger logger = LoggerFactory.getLogger(HelloJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //获取触发器的名称及其组名称,获取作业详细信息的名称及其组名称.
        String triggerName = context.getTrigger().getKey().getName();
        String triggerGroup = context.getTrigger().getKey().getGroup();
        String jobDetailName = context.getJobDetail().getKey().getName();
        String jobDetailGroup = context.getJobDetail().getKey().getGroup();
        logger.info("执行作业,作业名称={},作业所属组={},触发器名称={},触发器所属组={}",
                jobDetailName, jobDetailGroup, triggerName, triggerGroup);
    }
}
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class HelloTest {
    public static void main(String[] args) throws SchedulerException, InterruptedException {
        //1)读取 classpath 下的 quartz.properties(不存在就都使用默认值)配置来实例化 Scheduler
        //可以在类路径下使用同名文件覆盖 quartz-x.x.x.jar 包下的 org\quartz\quartz.properties 属性文件
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //2、定义作业的详细信息,并设置要执行的作业的类名。设置作业的名称及其组名.
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("helloJob", "jobGroup").build();
        //3、创建触发器,设置触发器名称与组名称,设置 CronTrigger 触发器的调度规则为每 10 秒触发一次.
        //startNow():表示立即触发任务,否则需要等待下一个触发点
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("helloTrigger", "triggerGroup")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")).build();
        //4、将 jobDetail 与 trigger 注册到调度器 scheduler 并启动。
        scheduler.scheduleJob(jobDetail, cronTrigger);
        scheduler.start();

        TimeUnit.MINUTES.sleep(1);//1分钟以后停掉调度器
        scheduler.shutdown();
    }
}

quartz-scheduler 核心 API

Scheduler · 调度器

1、Scheduler 用来对 Trigger 和 Job 进行管理,Trigger 和 JobDetail 可以注册到 Scheduler 中,两者在 Scheduler 中都拥有自己的唯一的组(group)和名称(name)用来进行彼此的区分,Scheduler 可以通过组和名称来对 Trigger 和 JobDetail 进行管理。
2、每个 Scheduler 都有一个 SchedulerContext,用来保存 Scheduler 的上下文数据,Job 和 Trigger 都可以获取其中的信息。
3、Scheduler 是由 SchedulerFactory 创建,它有两个实现:DirectSchedulerFactory 、StdSchdulerFactory ,前者可以用来在代码里定制 Schduler 参数,后者直接读取 classpath 下的 quartz.properties(不存在就都使用默认值)配置来实例化 Scheduler。

Job · 任务

1、Job 是一个任务接口,开发者可以实现该接口定义自己的任务,JobExecutionContext 中提供了调度上下文的各种信息。
2、Job 中的任务有可能并发执行,例如任务的执行时间过长,而每次触发的时间间隔太短,则会导致任务会被并发执行。如果是并发执行,就需要一个数据库锁去避免一个数据被多次处理。可以在 execute()方法上添加 @DisallowConcurrentExecution 注解解决这个问题。

JobDetail · 任务详情

1、JobDetail 对象是在将 job 注册到 scheduler 时,由客户端程序创建的,它包含 job 的各种属性设置,以及用于存储 job 实例状态信息的 JobDataMap。
2、JobDetail 由 JobBuilder 创建/定义,Quartz 不存储 Job 的实际实例,但是允许通过使用 JobDetail 定义一个实例。
3、Job 有一个与其关联的名称和组,应该在单个 Scheduler 中唯一标识它们。
4、一个 Trigger(触发器) 只能对应一个 Job(任务),但是一个 Job 可以对应多个 Trigger。JobDetal 与 Trigger 一对多

Trigger·触发器

1、Trigger 用于触发 Job 的执行。TriggerBuilder 用于定义/构建触发器实例。
2、Trigger也有一个相关联的 JobDataMap,用于给Job传递一些触发相关的参数。
3、Quartz自带了各种不同类型的 Trigger,最常用的主要是SimpleTrigger和CronTrigger。

JobDataMap 1、JobDataMap 实现了 JDK 的 Map 接口,可以以 Key-Value 的形式存储数据。
2、JobDetail、Trigger 实现类中都定义 JobDataMap 成员变量及其 getter、setter 方法,可以用来设置参数信息,Job 执行 execute() 方法的时候,JobExecutionContext 可以获取到 JobDataMap 中的信息。

1、Scheduler 实例化后,可以启动(start)、暂停(stand-by)、停止(shutdown)。

2、Scheduler 被停止后,除非重新实例化,否则不能重新启动;只有当 scheduler 启动后,trigger才会被触发(job才会被执行;暂停状态 trigger 不会触发执行任务。

3、Scheduler 生命周期:从 SchedulerFactory 创建它开始,到 Scheduler 调用 shutdown() 方法结束;Scheduler 被创建后,可以增加、删除和列举 Job 和 Trigger,以及执行其它与调度相关的操作(如暂停Trigger)。

4、SimpleTrigger 触发器主要用于一次性执行的 Job(只在某个特定的时间点执行一次),或者每间隔T个时间单位执行一次。CronTrigger 触发器基于日历进行调度,如“每个星期五的正午”,或者“每月的第十天的上午10:15”等。

5、Job 被创建后,可以保存在 Scheduler 中,与 Trigger 是独立的,同一个 Job 可以有多个 Trigger;这种松耦合的一个好处是可以修改或者替换 Trigger,而不用重新定义与之关联的 Job。

6、Job 和 Trigger 注册到 Scheduler 时,可以为它们设置 key,配置其身份属性。Job 和 Trigger 的key(JobKey和TriggerKey)可以用于将 Job 和 Trigger 放到不同的分组(group)里,然后基于分组进行操作。同一个分组下的 Job 或 Trigger 的名称必须唯一,即一个 Job 或 Trigger 的 key 由名称(name)和分组(group)组成。

quartz.properties Quartz Configuration

1、Quartz 使用一个名为 quartz.properties 的属性文件进行信息配置,必须位于 classpath 下,是 StdSchedulerFactory 用于创建 Scheduler 的默认属性文件。

2、默认情况下 StdSchedulerFactory 从类路径下加载名为 “quartz.properties” 的属性文件,如果失败,则加载 org/quartz 包中的“quartz.properties”文件,其默认内容如下:

#计划程序的名称
org.quartz.scheduler.instanceName: DefaultQuartzScheduler   
org.quartz.scheduler.rmi.export: false
org.quartz.scheduler.rmi.proxy: false
org.quartz.scheduler.wrapJobExecutionInUserTransaction: false

org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
#线程池中的线程个数.10个线程表示最多可以同时执行10个任务/作业
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000
#表示 quartz 中的所有数据,比如作业和触发器的信息都保存在内存中(而不是数据库中)
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

3、quartz.properties 可用属性的完整配置信息可以参考官网 Quartz Configuration,根据不同的需求,配置分类如下:

  1. Main Configuration (configuration of primary scheduler settings, transactions)
  2. Configuration of ThreadPool (tune resources for job execution)
  3. Configuration of Listeners (your application can receive notification of scheduled events)
  4. Configuration of Plug-Ins (add functionality to your scheduler)
  5. Configuration of RMI Server and Client (use a Quartz instance from a remote process)
  6. Configuration of RAMJobStore (store jobs and triggers in memory)
  7. Configuration of JDBC-JobStoreTX (store jobs and triggers in a database via JDBC)
  8. Configuration of JDBC-JobStoreCMT (JDBC with JTA container-managed transactions)
  9. Configuration of DataSources (for use by the JDBC-JobStores)
  10. Configuration of Database Clustering (achieve fail-over and load-balancing with JDBC-JobStore)
  11. Configuration of TerracottaJobStore (Clustering without a database!)

JobDataMap 任务数据对象

1、创建 JobDetail 时,将要执行的 job 的类名传给了 JobDetail,所以 scheduler 就知道了要执行何种类型的 job;每次当scheduler 执行 job 的 execute(…) 方法之前会创建该类的一个新的实例执行完毕,对该 Job 实例的引用就被丢弃了,实例会被垃圾回收

2、Job 实现类中必须有一个无参的构造函数(当使用默认的JobFactory时),Job 实现类中,不应该定义有状态的数据属性,因为在 job 的多次执行中,这些属性的值不会保留。应该使用 JobDataMap 给 Job 实例设置属性,用于在多次执行中跟踪 Job 的状态。

3、JobDetail、Trigger 实现类中都定义 JobDataMap 成员变量及其 getter、setter 方法,可以用来设置参数信息,Job 执行 execute() 方法的时候,JobExecutionContext 可以获取到 JobDataMap 中的信息。

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/**
 * @author wangmaoxiong
 */
public class HiJobTest {
    public static void main(String[] args) {
        try {
            //1)读取 classpath 下的 quartz.properties(不存在就都使用默认值)配置来实例化 Scheduler
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            //2)创建任务详情。设置任务名称与所属组名,同组内的任务名称必须唯一。
            // 为任务添加数据,可以直接 usingJobData,也可以先 jobDetail.getJobDataMap(),然后 put
            JobDetail jobDetail = JobBuilder.newJob(HiJob.class)
                    .withIdentity("hiJob", "hiJobGroup")
                    .usingJobData("url", "https://wangmaoxiong.blog.csdn.net/article/details/105057405")
                    .build();

            //3)设置触发器,设置触发器名称与所属组名,同组内的触发器名称必须唯一。
            // 为触发器添加数据,可以直接 usingJobData,也可以先 jobDetail.getJobDataMap(),然后 put
            // startNow() 表示启动后立即执行
            // withSchedule(ScheduleBuilder<SBT> scheduleBuilder):设置触发器调度计划,withIntervalInSeconds:间隔多少秒执行
            // repeatForever:表示用于重复。
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("hiTrigger", "hiTriGroup")
                    .startNow()
                    .usingJobData("totalCount", 3)
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                    .build();

            //4)注册任务详情与触发器,然后启动
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
 * @author wangmaoxiong
 * quartz 任务/作业
 */
public class HiJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //getJobDetail() 返回 JobDetail, getTrigger() 返回 Trigger,然后都可以获取自己的 JobDataMap 进行取值或者设值
        //context.getMergedJobDataMap() 返回的 JobDataMap 包含了 JobDetail 与 getTrigger 设置的所有属性
        JobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap();
        JobDataMap triggerDataMap = context.getTrigger().getJobDataMap();

        String url = jobDetailDataMap.getString("url");
        int totalCount = triggerDataMap.getInt("totalCount");
    }
}

1、context.getMergedJobDataMap() 返回的 JobDataMap 是 JobDetail 中的 JobDataMap 和 Trigger 中的 JobDataMap 的并集,但是如果存在相同的数据,则后者会覆盖前者的值。
2、如果在 Job 类中为 JobDataMap 中存储数据的 key 增加 setter 方法,那么 Quartz 的默认 JobFactory 实现会在 job 被实例化的时候自动调用这些 setter 方法进行注值,这样就不需要在 execute() 方法中显式地从 JobDataMap 中取数据了。

示例源码:https://github.com/wangmaoxiong/quartzapp/tree/master/src/test/java/com/wmx/quartzapp/helloworld

Job/JobDetail 实例 与 并发

1、一个 Job 实现类可以与多个 JobDetail 实例关联(JobDetal 与 Trigger 一对多),每一个 JobDetail 实例都有自己的属性集和 JobDataMap,最后将所有的实例都注册到 scheduler 中。如 "YearEndSettlementJob" 类实现 Job 接口,该 job 实现需要 JobdataMap 传入一个参数,表示年终结算的季度(1-4)。因此可以创建该 job 的多个实例(JobDetail),如 "jobDetail1"、"jobDetail2"、"jobDetail3"、"jobDetail4", 然后将季度"1,2,3,4"作为 JobDataMap 的属性传入。当一个 trigger 被触发时,与之关联的 JobDetail 实例会被加载,JobDetail 引用的 job 类通过配置在 Scheduler 上的 JobFactory 进行初始化,然后尝试调用 JobDataMap 中的 key 的 setter 方法注入属性值。、

2、Job 的状态数据(JobDataMap)并发性会被下面两个注解所影响:

@DisallowConcurrentExecution

添加到 Job 的实现类上,表示不要并发地执行 Job 实现的同一个实例(JobDetail)。假如上面的“YearEndSettlementJob”类上有该注解,则同一时刻仅允许执行一个“jobDetail1”实例,但可以并发地执行“jobDetail2”、"jobDetail3"、"jobDetail4" 实例。所以该并发限制实际是针对JobDetail 的,而不是 job 的

@PersistJobDataAfterExecution

添加到 Job 实现类上,表示调度器在成功执行了 job 类的 execute 方法后(没有发生任何异常),更新 JobDetail 中 JobDataMap 的数据,使得该 JobDetail 在下一次执行的时候JobDataMap 中是更新后的数据,而不是更新前的旧数据。

和 @DisallowConcurrentExecution 注解一样,尽管注解是加在 job 类上,但其限制作用是针对JobDetil 的,而不是 job 类。

3、生产环境如果任务执行存在并发问题,则强烈建议加上 @PersistJobDataAfterExecution 、@DisallowConcurrentExecution 注解,因为当同一个 JobDetail 实例被并发执行时,由于竞争,JobDataMap 中存储的数据很可能是不确定的。

4、当任务的执行时间过长,而触发的时间间隔小于执行时间,则会导致同一个 JobDetil 实例被并发执行。所以如果想要验证注解非常简单,只要将执行间隔缩小,然后 Job 实现类的 execute 方法中使用 Thread.sleep() 硬性延迟。查看任务执行情况,然后加上上面的注解才查看。源码如下:

https://github.com/wangmaoxiong/quartzapp/blob/master/src/test/java/com/wmx/quartzapp/helloworld/HiJobTest.java

https://github.com/wangmaoxiong/quartzapp/blob/master/src/test/java/com/wmx/quartzapp/helloworld/HiJob.java

5、通过 JobDetail 对象还可以给 job 实例配置的以下属性:

Durability 指示 job 是否是持久性的。如果 job 是非持久的,当没有活跃的 trigger 与之关联时,就会被自动地从 scheduler 中删除。即非持久的 job 的生命期是由 trigger 的存在与否决定的.
RequestsRecovery 指示  job 遇到故障重启后,是否是可恢复的。如果 job 是可恢复的,在其执行的时候,如果 scheduler 发生硬关闭(hard shutdown)(比如运行的进程崩溃了,或者关机了),则当 scheduler 重启时,该 job 会被重新执行。此时该 job 的JobExecutionContext.isRecovering() 返回true。
//2)创建任务详情。设置任务名称与所属组名,同组内的任务名称必须唯一。
// 为任务添加数据,可以直接 usingJobData,也可以先 jobDetail.getJobDataMap(),然后 put
JobDetail jobDetail = JobBuilder.newJob(HiJob.class)
	.withIdentity("hiJob", "hiJobGroup")
	.usingJobData("url", "https://wangmaoxiong.blog.csdn.net/article/details/105057405")
	.requestRecovery(true)  //设置  job 遇到故障重启后,是否是可恢复的,默认为 false.
	.storeDurably(true)     //设置  job 是否是持久性的,默认为 false.
	.build();

MVC 访问调度 Quartz 任务

1、为了学习 Quartz 方便,所以使用了 mian 运行,其实对于 web 应用操作步骤完全同理,如下源码所示:

https://github.com/wangmaoxiong/quartzapp/tree/master/src/main/java/com/wmx/quartzapp

2、其中 WeatherController 控制层提供访问接口,方法中使用 Quartz 调度器调度 WeatherRequestJob 作业,用于定时请求天气信息接口,获取城市的天气数据。

3、WeatherRequestJob 任务中使用 BeanFactoryAware 的方式获取 Spring 容器中的 RestTemplate 实例,然后请求 http 地址.

4、BeanConfig 类是 @Configuration 配置类,使用 @Bean 将 RestTemplate 实例交由 Spring 容器管理.

5、控制层接口返回的数据使用 ResultData 对象进行封装,包含 code、message、data 三部分,同时提供 ResultCode 枚举提供常用的返回值类型。

本文介绍的是内存存储,即所有的调度信息全部存储在内存中,如果需要使用 jdbc 存储到数据库中,则可以参考《Spring Boot 2.1.3 集成 Quartz 定时器, jdbc 持久化调度信息

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章