基于 Quartz 开发企业级任务调度应用.docx

上传人:b****6 文档编号:6206349 上传时间:2023-01-04 格式:DOCX 页数:13 大小:301.23KB
下载 相关 举报
基于 Quartz 开发企业级任务调度应用.docx_第1页
第1页 / 共13页
基于 Quartz 开发企业级任务调度应用.docx_第2页
第2页 / 共13页
基于 Quartz 开发企业级任务调度应用.docx_第3页
第3页 / 共13页
基于 Quartz 开发企业级任务调度应用.docx_第4页
第4页 / 共13页
基于 Quartz 开发企业级任务调度应用.docx_第5页
第5页 / 共13页
点击查看更多>>
下载资源
资源描述

基于 Quartz 开发企业级任务调度应用.docx

《基于 Quartz 开发企业级任务调度应用.docx》由会员分享,可在线阅读,更多相关《基于 Quartz 开发企业级任务调度应用.docx(13页珍藏版)》请在冰豆网上搜索。

基于 Quartz 开发企业级任务调度应用.docx

基于Quartz开发企业级任务调度应用

Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于Java实现。

作为一个优秀的开源调度框架,Quartz具有功能强大,应用灵活,易于集成的特点。

本文剖析了Quartz框架内部的基本实现原理,通过一些具体实例描述了应用Quartz开发应用程序的基本方法,并对企业应用中常见的问题及解决方案进行了讨论。

Quartz基本概念及原理

QuartzScheduler开源框架

Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于Java实现。

该项目于2009年被Terracotta收购,目前是Terracotta旗下的一个项目。

读者可以到 http:

//www.quartz-scheduler.org/站点下载Quartz的发布版本及其源代码。

笔者在产品开发中使用的是版本1.8.4,因此本文内容基于该版本。

本文不仅介绍如何应用Quartz进行开发,也对其内部实现原理作一定讲解。

作为一个优秀的开源调度框架,Quartz具有以下特点:

1.强大的调度功能,例如支持丰富多样的调度方法,可以满足各种常规及特殊需求;

2.灵活的应用方式,例如支持任务和调度的多种组合方式,支持调度数据的多种存储方式;

3.分布式和集群能力,Terracotta收购后在原来功能基础上作了进一步提升。

本文暂不讨论该部分内容

另外,作为Spring默认的调度框架,Quartz很容易与Spring集成实现灵活可配置的调度功能。

下面是本文中用到的一些专用词汇,在此声明:

scheduler:

任务调度器 trigger:

触发器,用于定义任务调度时间规则 job:

任务,即被调度的任务 misfire:

错过的,指本来应该被执行但实际没有被执行的任务调度 

Quartz任务调度的基本实现原理

核心元素

Quartz任务调度的核心元素是scheduler,trigger和job,其中trigger和job是任务调度的元数据,scheduler是实际执行调度的控制器。

在Quartz中,trigger是用于定义调度时间的元素,即按照什么时间规则去执行任务。

Quartz中主要提供了四种类型的trigger:

SimpleTrigger,CronTirgger,DateIntervalTrigger,和NthIncludedDayTrigger。

这四种trigger可以满足企业应用中的绝大部分需求。

我们将在企业应用一节中进一步讨论四种trigger的功能。

在Quartz中,job用于表示被调度的任务。

主要有两种类型的job:

无状态的(stateless)和有状态的(stateful)。

对于同一个trigger来说,有状态的job不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。

Job主要有两种属性:

volatility和durability,其中volatility表示任务是否被持久化到数据库存储,而durability表示在没有trigger关联的时候任务是否被保留。

两者都是在值为true的时候任务被持久化或保留。

一个job可以被多个trigger关联,但是一个trigger只能关联一个job。

在Quartz中,scheduler由scheduler工厂创建:

DirectSchedulerFactory或者StdSchedulerFactory。

第二种工厂StdSchedulerFactory使用较多,因为DirectSchedulerFactory使用起来不够方便,需要作许多详细的手工编码设置。

Scheduler主要有三种:

RemoteMBeanScheduler,RemoteScheduler和StdScheduler。

本文以最常用的StdScheduler为例讲解。

这也是笔者在项目中所使用的scheduler类。

Quartz核心元素之间的关系如下图所示:

图1.Quartz核心元素关系图 

线程视图

在Quartz中,有两类线程,Scheduler调度线程和任务执行线程,其中任务执行线程通常使用一个线程池维护一组线程。

图2.Quartz线程视图 

Scheduler调度线程主要有两个:

执行常规调度的线程,和执行misfiredtrigger的线程。

常规调度线程轮询存储的所有trigger,如果有需要触发的trigger,即到达了下一次触发的时间,则从任务执行线程池获取一个空闲线程,执行与该trigger关联的任务。

Misfire线程是扫描所有的trigger,查看是否有misfiredtrigger,如果有的话根据misfire的策略分别处理。

下图描述了这两个线程的基本流程:

图3.Quartz调度线程流程图 

关于misfiredtrigger,我们在企业应用一节中将进一步描述。

数据存储

Quartz中的trigger和job需要存储下来才能被使用。

Quartz中有两种存储方式:

RAMJobStore,JobStoreSupport,其中RAMJobStore是将trigger和job存储在内存中,而JobStoreSupport是基于jdbc将trigger和job存储到数据库中。

RAMJobStore的存取速度非常快,但是由于其在系统被停止后所有的数据都会丢失,所以在通常应用中,都是使用JobStoreSupport。

在Quartz中,JobStoreSupport使用一个驱动代理来操作trigger和job的数据存储:

StdJDBCDelegate。

StdJDBCDelegate实现了大部分基于标准JDBC的功能接口,但是对于各种数据库来说,需要根据其具体实现的特点做某些特殊处理,因此各种数据库需要扩展StdJDBCDelegate以实现这些特殊处理。

Quartz已经自带了一些数据库的扩展实现,可以直接使用,如下图所示:

图4.Quartz数据库驱动代理 

作为嵌入式数据库的代表,Derby近来非常流行。

如果使用Derby数据库,可以使用上图中的CloudscapeDelegate作为trigger和job数据存储的代理类。

 

基本开发流程及简单实例

搭建开发环境

利用Quartz进行开发相当简单,只需要将下载开发包中的quartz-all-1.8.4.jar加入到classpath即可。

根据笔者的经验,对于任务调度功能比较复杂的企业级应用来说,最好在开发阶段将Quartz的源代码导入到开发环境中来。

一方面可以通过阅读源码了解Quartz的实现机理,另一方面可以通过扩展或修改Quartz的一些类来实现某些Quartz尚不提供的功能。

图5.Quartz实例工程及源码导入 

上图中左边是源码导入后的截图,其中org.quartz.*即为quartz的源码。

导入源码后可能会有一些编译错误,通常出现在org.quartz.ee.*和org.quartz.jobs.ee.*包中。

下载开发包中有一个lib目录,读者可以将该目录下的jar文件加入到编译环境。

如果还有编译错误,读者可以参考上图中右侧的jar列表,到网上去搜索下载。

项目中com.ibm.zxn.sample.quartz是我们自己的类包,下面的实例中我们会用到它。

一个简单实例

Quartz开发包中有一个examples目录,其中有15个基本实例。

建议读者阅读并实践这些例子。

本文这里只列举一个小的实例,介绍基本的开发方法。

1.准备数据库和Quartz用的数据表

1.本文使用IBMDB2数据库:

将jdbc驱动程序db2jcc.jar加入到项目中;

2.在数据库中创建一个新库QUARTZDB;

3.执行/quartz-1.8.4/docs/dbTables/tables_db2_v8.sql,创建数据表;表建好后如下所示:

图6.Quartz数据表

2.准备配置文件,加入到项目中

图7.实例配置文件

3.通过实现job接口定义我们自己的任务类,如下所示:

图8.定义任务类

4.然后,实现任务调度的主程序,如下所示:

本实例中,我们利用DateIntervalTrigger实现一个每两分钟执行一次的任务调度。

图9.实现主程序

5.完成后项目结构如下所示:

图10.实例项目结构图

6.运行程序,查看数据库表和运行结果

数据库中,QRTZ_TRIGGERS表中添加了一条trigger记录,如下所示:

图11.QRTZ_TRIGGERS表中的记录

QRTZ_JOB_DETAILS表中添加了一条job记录,如下所示:

图12.QRTZ_JOB_DETAILES表中的记录

从运行结果来看,任务每两分钟被执行一次:

图13.运行结果

企业级开发中的常见应用

在应用Quartz进行企业级的开发时,有一些问题会经常遇到。

本节笔者根据自己在项目开发中的经验,介绍企业开发中常见的一些问题以及通常的解决办法。

应用一:

如何使用不同类型的Trigger

前面我们提到Quartz中四种类型的Trigger:

SimpleTrigger,CronTirgger,DateIntervalTrigger,和NthIncludedDayTrigger。

SimpleTrigger一般用于实现每隔一定时间执行任务,以及重复多少次,如每2小时执行一次,重复执行5次。

SimpleTrigger内部实现机制是通过计算间隔时间来计算下次的执行时间,这就导致其不适合调度定时的任务。

例如我们想每天的1:

00AM执行任务,如果使用SimpleTrigger的话间隔时间就是一天。

注意这里就会有一个问题,即当有misfired的任务并且恢复执行时,该执行时间是随机的(取决于何时执行misfired的任务,例如某天的3:

00PM)。

这会导致之后每天的执行时间都会变成3:

00PM,而不是我们原来期望的1:

00AM。

CronTirgger类似于LINUX上的任务调度命令crontab,即利用一个包含7个字段的表达式来表示时间调度方式。

例如,"01510**?

*"表示每天的10:

15AM执行任务。

对于涉及到星期和月份的调度,CronTirgger是最适合的,甚至某些情况下是唯一选择。

例如,"01014?

3WED"表示三月份的每个星期三的下午14:

10PM执行任务。

读者可以在具体用到该trigger时再详细了解每个字段的含义。

DateIntervalTrigger是Quartz1.7之后的版本加入的,其最适合调度类似每N(1,2,3...)小时,每N天,每N周等的任务。

虽然SimpleTrigger也能实现类似的任务,但是DateIntervalTrigger不会受到我们上面说到的misfired任务的影响。

另外,DateIntervalTrigger也不会受到DST(DaylightSavingTime,即中国的夏令时)调整的影响。

笔者就曾经因为该原因将项目中的SimpleTrigger改为了DateIntervalTrigger,因为如果使用SimpleTrigger,本来设定的调度时间就会由于DST的调整而提前或延迟一个小时,而DateIntervalTrigger不会受此影响。

NthIncludedDayTrigger的用途比较简单明确,即用于每隔一个周期的第几天调度任务,例如,每个月的第3天执行指定的任务。

除了上面提到的4种Trigger,Quartz中还定义了一个Calendar类(注意,是org.quartz.Calendar)。

这个Calendar与Trigger一起使用,但是它们的作用相反,它是用于排除任务不被执行的情况。

例如,按照Trigger的规则在10月1号需要执行任务,但是Calendar指定了10月1号是节日(国庆),所以任务在这一天将不会被执行。

通常来说,Calendar用于排除节假日的任务调度,从而使任务只在工作日执行。

应用二:

使用有状态(StatefulJob)还是无状态的任务(Job)

在Quartz中,Job是一个接口,企业应用需要实现这个接口以定义自己的任务。

基本来说,任务分为有状态和无状态两种。

实现Job接口的任务缺省为无状态的。

Quartz中还有另外一个接口StatefulJob。

实现StatefulJob接口的任务为有状态的,上一节的简单实例中,我们定义的SampleJob就是实现了StatefulJob接口的有状态任务。

下图列出了Quartz中Job接口的定义以及一些自带的实现类:

图14.Quartz中Job接口定义 

无状态任务一般指可以并发的任务,即任务之间是独立的,不会互相干扰。

例如我们定义一个trigger,每2分钟执行一次,但是某些情况下一个任务可能需要3分钟才能执行完,这样,在上一个任务还处在执行状态时,下一次触发时间已经到了。

对于无状态任务,只要触发时间到了就会被执行,因为几个相同任务可以并发执行。

但是对有状态任务来说,是不能并发执行的,同一时间只能有一个任务在执行。

在笔者项目中,某些任务需要对数据库中的数据进行增删改处理。

这些任务不能并发执行,否则会造成数据混乱。

因此我们使用StatefulJob接口。

现在回到上面的例子,任务每2分钟执行一次,若某次任务执行了5分钟才完成,Quartz会怎么处理呢?

按照trigger的规则,第2分钟和第4分钟分别会有一次预定的触发执行,但是由于是有状态任务,因此实际不会被触发。

在第5分钟第一次任务执行完毕时,Quartz会把第2和第4分钟的两次触发作为misfiredjob进行处理。

对于misfiredjob,Quartz会查看其misfire策略是如何设定的,如果是立刻执行,则会马上启动一次执行,如果是等待下次执行,则会忽略错过的任务,而等待下次(即第6分钟)触发执行。

读者可以在自己的项目中体会两种任务的区别以及Quartz的处理方法,根据具体情况选择不同类型的任务。

应用三:

如何设置Quartz的线程池和并发任务

Quartz中自带了一个线程池的实现:

SimpleThreadPool。

类如其名,这只是线程池的一个简单实现,没有提供动态自发调整等高级特性。

Quartz提供了一个配置参数:

org.quartz.threadPool.threadCount,可以在初始化时设定线程池的线程数量,但是一次设定后不能再修改。

假定这个数目是10,则在并发任务达到10个以后,再有触发的任务就无法被执行了,只能等待有空闲线程的时候才能得到执行。

因此有些trigger就可能被misfire。

但是必须指出一点,这个初始线程数并不是越大越好。

当并发线程太多时,系统整体性能反而会下降,因为系统把很多时间花在了线程调度上。

根据一般经验,这个值在10--50比较合适。

对于一些注重性能的线程池来说,会根据实际线程使用情况进行动态调整,例如初始线程数,最大线程数,空闲线程数等。

读者在应用中,如果有更好的线程池,则可以在配置文件中通过下面参数替换SimpleThreadPool:

org.quartz.threadPool.class=myapp.GreatThreadPool。

应用四:

如何处理Misfired任务

在Quartz应用中,misfiredjob是经常遇到的情况。

一般来说,下面这些原因可能造成misfiredjob:

1)系统因为某些原因被重启。

在系统关闭到重新启动之间的一段时间里,可能有些任务会

被misfire;

2)Trigger被暂停(suspend)的一段时间里,有些任务可能会被misfire;

3)线程池中所有线程都被占用,导致任务无法被触发执行,造成misfire;

4)有状态任务在下次触发时间到达时,上次执行还没有结束;

为了处理misfiredjob,Quartz中为trigger定义了处理策略,主要有下面两种:

MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:

针对misfiredjob马上执行一次;

MISFIRE_INSTRUCTION_DO_NOTHING:

忽略misfiredjob,等待下次触发;

建议读者在应用开发中,将该设置作为可配置选项,使得用户可以在使用过程中,针对已经添加的tirgger动态配置该选项。

应用五:

如何保留已经结束的Trigger

在Quartz中,一个tirgger在最后一次触发完成之后,会被自动删除。

Quartz默认不会保留已经结束的trigger,如下面Quartz源代码所示:

图15.executionComplete()源码 

但是在实际应用中,有些用户需要保留以前的trigger,作为历史记录,或者作为以后创建其他trigger的依据。

如何保留结束的trigger呢?

一个办法是应用开发者自己维护一份数据备份记录,并且与Quartz原表的记录保持一定的同步。

这个办法实际操作起来比较繁琐,而且容易出错,不推荐使用。

另外一个办法是通过修改并重新编译Quartz的trigger类,修改其默认的行为。

我们以org.quartz.SimpleTrigger为例,修改上面代码中if(!

mayFireAgain())部分的代码如下:

图16.修改executionComplete()源码 

另外我们需要在SimpleTrigger中定义一个新的类属性:

needRetain,如下所示:

图17.定义新属性needRetain 

在定义自己的trigger时,设置该属性,就可以选择是否在trigger结束时删除trigger。

如下代码所示:

图18.使用修改后的SimpleTrigger 

有人可能会考虑通过定义一个新的类,然后继承org.quartz.SimpleTrigger类并覆盖executionComplete()方法来实现。

但是这种方法是行不通的,因为Quartz内部在处理时会根据trigger的类型重新生成SimpleTrigger类的实例,而不是使用我们自己定义的类创建的实例。

这一点应该是Quartz的一个小小的不足之处,因为它把扩展trigger的能力堵死了。

好在Quartz是开源的,我们可以根据需要进行修改。

小结

作为当前颇具生命力的开源框架,Quartz已经得到了广泛的应用。

Quartz的强大功能和应用灵活性,在企业应用中发挥了巨大的作用。

本文描述了如何应用Quartz开发应用程序,并对企业应用中常见的问题及解决方案进行了讨论。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 表格模板 > 合同协议

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1