史上最全的企业级定时任务框架 Quartz 介绍

Quartz是一个功能丰富的开源作业调度库,可以集成到几乎任何Java应用程序中——从最小的独立应用程序到最大的电子商务系统。Quartz可用于创建简单或复杂的调度,用于执行数十、数百甚至数万个任务;任务被定义为标准Java组件的作业,可以执行您可以为其编写的任何操作。Quartz调度器包括许多企业级特性,比如对JTA事务和集群的支持。

1、 Quartz 核心 API

Quartz API 的关键接口有:

  • Scheduler - 用于与调度程序交互的主 API。
  • Job — 由希望由调度器执行的组件实现的接口。
  • JobDetail — 用于定义作业的实例。
  • Trigger — 一个组件,它定义了一个给定作业将在其上执行的调度。
  • JobBuilder — 用于定义/构建定义作业实例的 JobDetail 实例。
  • TriggerBuilder — 用于定义/构建触发器实例。

Scheduler(调度器)的生命周期由它的创建限定,通过 SchedulerFactory 和对其 shutdown()方法的调用。一旦创建了Scheduler 接口,就可以使用它添加、删除和列出作业和触发器,并执行其他与调度相关的操作(比如暂停触发器)。但是,在使用start()方法启动调度器之前,它实际上不会对任何触发器进行操作(执行作业).

2、 Quartz 使用 Demo

下面我们通过一个简单的例子来入门一下 Quartz,看一下 Quartz 是如果使用的。

首先创建一个我们需要定时执行的任务,它需要实现 org.quartz.Job:

public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date());
        System.out.println("HelloJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100));

    }
}

然后我们定义 SchedulerJobDetail 以及 Trigger 在 Quartz 里面的核心接口来调度我们上面的任务。

public class JobTest {

    public static void main(String[] args) throws SchedulerException, InterruptedException {
        // 1、创建调度器Scheduler
        SchedulerFactory schedFact = new org.quartz.impl.StdSchedulerFactory();
        Scheduler sched = schedFact.getScheduler();

        // 2、创建JobDetail实例,并与HelloJob类绑定(Job执行内容)
        JobDetail job = JobBuilder.newJob(HelloJob.class)
                .withIdentity("myJob", "group1")
                .build();

        // 3、构建Trigger实例,每隔5s执行一次
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("myTrigger", "group1")
                .startNow()
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(5)
                        .repeatForever())
                .build();

        // 4、执行
        sched.start();
        sched.scheduleJob(job, trigger);
    }

}

3、Jobs and Triggers

Job是实现Job接口的类,它只有一个简单的方法:

Job.class

  package org.quartz;

  public interface Job {

    public void execute(JobExecutionContext context)
      throws JobExecutionException;
  }

当作业的触发器触发时(稍后将详细介绍),调度程序的一个工作线程将调用 execute(..) 方法。传递给此方法的JobExecutionContext 对象为作业实例提供了关于其“运行时”环境的信息—执行它的调度器的句柄、触发执行的触发器的句柄、作业的JobDetail对象和一些其他项。

JobDetail对象是由 Quartz 客户端(您的程序)在将作业(Job)添加到调度器时创建的。它包含作业的各种属性设置,以及JobDataMap,可用于存储作业类的给定实例的状态信息。它本质上是作业实例的定义。

Trigger (触发器)对象用于触发作业的执行(或“触发”)。当您希望调度作业时,可以实例化触发器并“调优”其属性,以提供您希望的调度。触发器还可能有一个与之关联的JobDataMap——这对于将参数传递给特定于触发触发器的作业非常有用。Quartz 有几种不同的触发器类型,但最常用的类型是SimpleTriggerCronTrigger

如果您需要 一次性 执行(在给定时刻只执行一个作业),或者如果您需要在给定时间触发一个作业,并让它重复N次,两次执行之间的延迟为T,那么SimpleTrigger非常方便。如果您希望基于类似日历的日程安排(如“每周五中午”或“每个月10日10点15分”)进行触发,那么 CronTrigger 非常有用。

为什么Quartz 中有 JobTriger 这两个概念?许多作业调度器没有作业和触发器的独立概念。有些人将“作业”简单地定义为执行时间(或调度)以及一些小的作业标识符。其他一些类似于 Quartzjobtrigger 对象的结合。在开发Quartz 时,我们认为在日程安排和要在该日程上执行的工作之间创建一个分离是有意义的。这(在我们看来)有许多好处。

例如,可以独立于触发器在作业调度器中创建和存储作业,并且可以将多个触发器与同一作业关联。这种松耦合的另一个好处是,可以配置在相关触发器过期后仍保留在调度程序中的作业,这样可以在以后重新调度作业,而不必重新定义它。它还允许您修改或替换触发器,而不必重新定义其关联的作业。

并多的 JobTriggers 信息请点击相就链接。

4、TriggerListeners、JobListeners 和 SchedulerListeners

侦听器是您创建的对象,用于根据调度程序中发生的事件执行操作。您可能已经猜到,Triggerlistener 接收与触发器相关的事件 Joblistener 接收与作业相关的事件。Schedulerlistener 非常类似于TriggerlistenerJoblistener,除了它们接收调度器本身内的事件通知—不一定是与特定触发器或作业相关的事件。

触发器相关的事件包括:触发器触发、触发器误触发(在本文档的“触发器”一节中讨论)和触发器完成(触发器触发的作业已经完成)。

4.1 XXXListener 接口定义

TriggerListener.java

public interface TriggerListener {

    public String getName();

    public void triggerFired(Trigger trigger, JobExecutionContext context);

    public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context);

    public void triggerMisfired(Trigger trigger);

    public void triggerComplete(Trigger trigger, JobExecutionContext context,
            int triggerInstructionCode);
}

与作业相关的事件包括:作业即将执行的通知,以及作业完成执行时的通知。

JobListener

public interface JobListener {

    public String getName();

    public void jobToBeExecuted(JobExecutionContext context);

    public void jobExecutionVetoed(JobExecutionContext context);

    public void jobWasExecuted(JobExecutionContext context,
            JobExecutionException jobException);

}

与调度器相关的事件包括:作业/触发器的添加、作业/触发器的删除、调度器内的严重错误、调度器被关闭的通知,以及其他事件。

public interface SchedulerListener {

    public void jobScheduled(Trigger trigger);

    public void jobUnscheduled(String triggerName, String triggerGroup);

    public void triggerFinalized(Trigger trigger);

    public void triggersPaused(String triggerName, String triggerGroup);

    public void triggersResumed(String triggerName, String triggerGroup);

    public void jobsPaused(String jobName, String jobGroup);

    public void jobsResumed(String jobName, String jobGroup);

    public void schedulerError(String msg, SchedulerException cause);

    public void schedulerStarted();

    public void schedulerInStandbyMode();

    public void schedulerShutdown();

    public void schedulingDataCleared();
}

4.2 自定义监听器

要创建侦听器,只需创建一个实现 org.quartz.TriggerListenerorg.quartz.JobListener 接口。然后,侦听器在运行时向调度程序注册,并且必须提供一个名称(或者,它们必须通过getName()方法发布自己的名称)。

为了方便起见,除了实现这些接口之外,您的类还可以扩展类JobListenerSupportTriggerListenerSupport,并简单地覆盖感兴趣的事件。

侦听器在运行时向调度程序注册,并且不会与作业和触发器一起存储在 JobStore 中。这是因为侦听器通常是应用程序的集成点。因此,每次应用程序运行时,都需要向调度器重新注册侦听器

添加对特定工作感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, KeyMatcher.jobKeyEquals(new JobKey("myJobName", "myJobGroup")));

你可能想为匹配器和键类使用静态导入,这将使你的匹配器定义更清晰:

import static org.quartz.JobKey.*;
import static org.quartz.impl.matchers.KeyMatcher.*;
import static org.quartz.impl.matchers.GroupMatcher.*;
import static org.quartz.impl.matchers.AndMatcher.*;
import static org.quartz.impl.matchers.OrMatcher.*;
import static org.quartz.impl.matchers.EverythingMatcher.*;
...等待

添加对特定组的所有作业感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, jobGroupEquals("myJobGroup"));

添加对两个特定组的所有作业感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, or(jobGroupEquals("myJobGroup"), jobGroupEquals("yourGroup")));

添加一个对所有作业都感兴趣的JobListener:

scheduler.getListenerManager().addJobListener(myJobListener, allJobs());

Schedulerlistener注册到调度器的 ListenerManager。实际上,Schedulerlistener可以是实现 org.quartz 的任何对象。SchedulerListener 接口。

添加一个SchedulerListener:

scheduler.getListenerManager().addSchedulerListener(mySchedListener);

删除一个SchedulerListener:

scheduler.getListenerManager().removeSchedulerListener(mySchedListener);

…注册triggerlistener的工作方式与此相同。

Quartz的大多数用户都不使用侦听器,但当应用程序需求需要事件通知时,侦听器非常方便,而作业本身不必显式地通知应用程序。

5、Job Stores

JobStore 负责跟踪您给调度器的所有“工作数据”:作业、触发器、日历等等。为您的 Quartz 调度器实例选择适当的JobStore是一个重要步骤。幸运的是,一旦你理解了它们之间的区别,选择应该是非常容易的。在向用于生成调度器实例的调度器提供的属性文件(或对象)中,声明调度器应该使用哪个JobStore(以及它的配置设置)。

永远不要在代码中直接使用JobStore实例。由于某些原因,许多人尝试这样做。JobStore用于Quartz本身的幕后使用。您必须(通过配置)告诉Quartz使用哪个JobStore,但随后您应该只在代码中使用Scheduler接口。

在 Quartz 中 Job 的存储形式有以下几种:

  • RAMJobStore
  • JDBCJobStore
  • TerracottaJobStore

5.1 RAMJobStore

RAMJobStore是使用起来最简单的JobStore,也是性能最好的(就CPU时间而言)。RAMJobStore以一种显而易见的方式获得它的名称:它将所有数据保存在RAM中。这就是为什么它像闪电一样快,也就是为什么它配置起来如此简单。缺点是,当应用程序结束(或崩溃)时,所有的调度信息都会丢失——这意味着RAMJobStore不能支持作业和触发器上的“非挥发性”设置。对于某些应用程序,这是可以接受的——甚至是期望的行为,但对于其他应用程序,这可能是灾难性的。

要使用RAMJobStore(假设您使用的是StdSchedulerFactory),只需指定类名org.quartz.simpl。用作配置quartz的JobStore类属性:

配置 Quartz 以使用RAMJobStore:

org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore

没有其他需要担心的设置。

5.2 JDBCJobStore

JDBCJobStore 的名称也很贴切——它通过 JDBC 将其所有数据保存在数据库中。因此,它的配置要比RAMJobStore 更复杂一些,而且速度也不如它快。但是,性能回调并不十分糟糕,特别是在构建具有主键索引的数据库表时。在具有良好LAN(在调度器和数据库之间)的相当现代的机器集合上,检索和更新触发触发器的时间通常不到10毫秒。

JDBCJobStore几乎可以与任何数据库一起工作,它已经被广泛用于Oracle、PostgreSQL、MySQL、MS SQLServer、HSQLDB和DB2。要使用JDBCJobStore,必须首先为Quartz创建一组数据库表。您可以在Quartz发行版的“docs/dbTables”目录中找到创建表的SQL脚本。如果还没有适合您的数据库类型的脚本,那么只需要查看一个现有的脚本,然后根据您的数据库以任何必要的方式修改它。需要注意的一点是,在这些脚本中,所有表都以前缀“QRTZ_”开始(比如表“QRTZ_TRIGGERS”和“QRTZ_JOB_DETAIL”)。这个前缀实际上可以是你想要的任何东西,只要你通知JDBCJobStore这个前缀是什么(在你的Quartz属性中)。对于在同一个数据库中创建多个表集和多个调度程序实例,使用不同的前缀可能非常有用。

创建了表之后,在配置和启动JDBCJobStore之前,还需要做出一个重要决策。您需要决定应用程序需要哪种类型的事务。如果您不需要将调度命令(如添加和删除触发器)绑定到其他事务,那么您可以让Quartz通过使用JobStoreTX作为您的JobStore来管理事务(这是最常见的选择)。

如果您需要Quartz与其他事务一起工作(例如,在J2EE应用服务器中),那么您应该使用JobStoreCMT——在这种情况下,Quartz将让应用服务器容器管理事务。

最后一部分是设置一个数据源,JDBCJobStore可以从该数据源连接到您的数据库。在Quartz属性中使用几种不同的方法之一定义数据源。一种方法是让Quartz创建和管理数据源本身——通过为数据库提供所有连接信息。另一种方法是让Quartz使用由运行在其内部的应用服务器管理的数据源——通过提供JDBCJobStore数据源的JNDI名称。有关属性的详细信息,请参考“docs/config”文件夹中的示例配置文件。

要使用JDBCJobStore(假设您使用的是StdSchedulerFactory),首先需要将您的Quartz配置的JobStore类属性设置为org. quartz.imp.jdbcjobstore。JobStoreTX或org.quartz.impl.jdbcjobstore。JobStoreCMT——这取决于你根据上面几段的解释所做的选择。

配置Quartz以使用JobStoreTx

org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX

接下来,您需要为JobStore选择一个要使用的驱动程序委托。驱动委托负责执行特定数据库可能需要的任何JDBC工作。StdJDBCDelegate是一个使用“普通的”JDBC代码(和SQL语句)来完成其工作的委托。如果没有另一个专门为您的数据库创建的委托,请尝试使用这个委托——我们只为使用StdJDBCDelegate发现问题的数据库创建了特定于数据库的委托(似乎是最严重的!)其他委托可以在" org.quartz.impl "中找到。jdbcjobstore”包,或在它的子包中。其他委托包括DB2v6Delegate(用于DB2 version 6和更早版本)、HSQLDBDelegate(用于HSQLDB)、MSSQLDelegate(用于Microsoft SQLServer)、PostgreSQLDelegate(用于PostgreSQL)、WeblogicDelegate(用于使用由Weblogic制作的JDBC驱动程序)、OracleDelegate(用于使用Oracle)等。

选择委托后,将其类名设置为JDBCJobStore要使用的委托。

配置JDBCJobStore以使用驱动委托:

org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate

接下来,您需要通知JobStore您正在使用什么表前缀(上面讨论过)。

org.quartz.jobStore.tablePrefix = QRTZ_

最后,您需要设置JobStore应该使用哪个数据源。还必须在Quartz属性中定义已命名的数据源。在本例中,我们指定Quartz应该使用数据源名称“myDS”(在配置属性的其他地方定义)。

使用要使用的数据源的名称配置JDBCJobStore:

org.quartz.jobStore.dataSource = myDS

如果你的调度程序很忙(即几乎总是执行与线程池大小相同数量的任务,那么你可能应该将数据源中的连接数量设置为线程池的大小+ 2。
org.quartz.jobStore.useProperties的配置参数可以设置为“true”(默认为false),以便指示JDBCJobStore, JobDataMaps中的所有值都将是字符串,因此可以存储为名称-值对,而不是在BLOB列中以序列化的形式存储更复杂的对象。从长远来看,这样做要安全得多,因为这样可以避免将非字符串类序列化为BLOB时出现的类版本控制问题。

5.3 TerracottaJobStore

TerracottaJobStore提供了一种无需使用数据库即可伸缩和健壮性的方法。这意味着您的数据库可以从 Quartz 获得免费的负载,而可以为应用程序的其余部分保存数据库的所有资源。

TerracottaJobStore可以集群或非集群运行,在任何一种情况下,都为你的作业数据提供了一种存储介质,这种介质在应用程序重启之间是持久的,因为数据存储在Terracotta服务器中。它的性能比通过JDBCJobStore使用数据库要好得多(大约好一个数量级),但比RAMJobStore慢得多。

要使用 TerracottaJobStore(假设您使用的是StdSchedulerFactory),只需指定类名org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore作为你用来配置quartz的JobStore类属性,并额外添加一行配置来指定Terracotta服务器的位置:

配置 Quartz 使用 TerracottaJobStore:

org.quartz.jobStore.class = org.terracotta.quartz.TerracottaJobStore
org.quartz.jobStore.tcConfigUrl = localhost:9510

更多关于JobStoreTerracotta 的信息可以在 http://www.terracotta.org/quartz

6、配置,资源使用和调度器

Quartz 的架构是模块化的,因此要让它运行,需要将几个组件“捆绑”在一起。幸运的是,有一些助手可以帮助实现这一点。

Quartz 可以工作之前需要配置的主要组件有:

  • ThreadPool
  • JobStore
  • DataSources (if necessary)
  • The Scheduler itself

ThreadPool(线程池)提供了一组线程供Quartz在执行作业时使用。池中的线程越多,可以并发运行的作业的数量就越多。但是,过多的线程可能会使系统陷入瘫痪。大多数 Quartz 用户发现 5个左右的线程 已经足够了—— 因为它们在任何时候都只有不到 100 个任务,这些任务通常不会被安排同时运行,而且这些任务是短暂的(很快就会完成)。其他用户发现,他们需要10个、15个、50个甚至100个线程——因为他们有成千上万个具有不同调度的触发器——在任何给定时刻,平均需要执行 10 到 100 个作业。为调度器池找到合适的大小完全取决于您使用调度器的目的。除了保持线程的数量尽可能小(考虑到您的机器资源)之外,没有什么真正的规则 —— 但是要确保您有足够的线程来按时完成任务。注意,如果触发器的触发时间到了,并且没有可用的线程,那么Quartz将阻塞(暂停)直到有可用的线程出现,然后任务将执行 —— 比它应该执行的时间晚了一些毫秒。这甚至可能导致线程失败 —— 如果在调度程序配置的“失败阈值”期间没有可用的线程。

在org.quartz 中定义了ThreadPool(线程池)接口。您可以以任何喜欢的方式创建线程池实现。Quartz附带了一个简单(但非常令人满意)的线程池,名为 org.Quartz.simple.simplethreadpool。这个线程池只是在它的池中维护一组固定的线程——不会增长,也不会收缩。但除此之外,它还相当健壮,并且经过了很好的测试——因为几乎所有使用 Quartz 的人都使用这个池。

上面讨论了 JobStoresDataSources。这里值得注意的是,所有 Jobstore 都实现了 org.quartz.spi。JobStore 接口——如果绑定的某个JobStore不符合您的需要,那么您可以创建自己的。

最后,您需要创建调度程序实例。需要给调度器本身一个名称,告知它的RMI设置,并传递JobStore和ThreadPool的实例。RMI设置包括调度器是否应该将自己创建为RMI的服务器对象(使自己对远程连接可用)、使用什么主机和端口,等等。StdSchedulerFactory(下面将讨论)还可以生成调度器实例,这些实例实际上是远程进程中创建的调度器的代理(RMI存根)。

StdSchedulerFactory

StdSchedulerFactory是org.quartz的一个实现。SchedulerFactory接口。它使用一组属性(java.util.Properties)创建和初始化Quartz调度器。属性通常存储在文件中并从文件中加载,但也可以由程序创建并直接交给工厂。在工厂上简单地调用getScheduler()将生成调度器,初始化它(及其线程池、JobStore和数据源),并返回其公共接口的句柄。

在Quartz发行版的“docs/config”目录中有一些示例配置(包括属性描述)。您可以在Quartz文档的“参考”部分的“配置”手册中找到完整的文档。

DirectSchedulerFactory

DirectSchedulerFactory是另一个SchedulerFactory实现。对于那些希望以更程序化的方式创建调度程序实例的人来说,它非常有用。通常不鼓励使用它,原因如下:(1)它要求用户对他们正在做的事情有更深刻的理解;(2)它不允许声明式配置——换句话说,您最终需要硬编码调度器的所有设置。

Logging

Quartz使用 SLF4J 框架来满足其所有日志记录需求。为了“调优”日志设置(比如输出的数量和输出的位置),您需要了解 SLF4J 框架,这超出了本文的范围。

如果您希望捕获关于触发和作业执行的额外信息,您可能会对启用 org.quartz.plugins.history.LoggingJobHistoryPluginorg.quartz.plugins.history.LoggingTriggerHistoryPlugin

7、其它

Plug-Ins

Quartz 提供了一个接口(org.quartz.spi.SchedulerPlugin),用于插入其他功能。

随 Quartz 一起提供各种实用功能的插件可以在 org.quartz.plugins 中找到文档。插件包。它们提供了诸如在调度器启动时自动调度作业、记录作业和触发事件的历史记录以及确保调度器在JVM退出时干净地关闭等功能。

JobFactory

当触发器触发时,它关联的作业将通过调度器上配置的 JobFactory 实例化。默认的 JobFactory 只是在job类上调用newInstance()。您可能希望创建自己的JobFactory实现来完成一些事情,比如让应用程序的IoC或DI容器生成/初始化作业实例。

看到 org.quartz.spi.JobFactory 和关联的 Scheduler.setJobFactory(fact) 方法。

“Factory-Shipped”工作

Quartz 还提供了许多实用程序作业,您可以在应用程序中使用它们来执行诸如发送电子邮件和调用 EJB 之类的操作。这些开箱即用的作业可以在 org.quartz.jobs 包中找到。

参考文章:quartz-2.3.0

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值