任务调度框架 Quartz

2021-12-06 17:02:40 浏览数 (1)

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

0 人点赞