自建网站平台有哪些,win优化大师有用吗,网页版微信二维码传送助手,网站建设好后有些什么资料本文转自#xff1a;http://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/ Quartz 基本概念及原理 Quartz Scheduler 开源框架 Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目#xff0c;完全基于 Java 实现。该项目于 2009 年被 Terracotta 收购http://www.ibm.com/developerworks/cn/opensource/os-cn-quartz/ Quartz 基本概念及原理 Quartz Scheduler 开源框架 Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目完全基于 Java 实现。该项目于 2009 年被 Terracotta 收购目前是 Terracotta 旗下的一个项目。读者可以到 http://www.quartz-scheduler.org/站点下载 Quartz 的发布版本及其源代码。笔者在产品开发中使用的是版本 1.8.4因此本文内容基于该版本。本文不仅介绍如何应用 Quartz 进行开发也对其内部实现原理作一定讲解。 作为一个优秀的开源调度框架Quartz 具有以下特点 强大的调度功能例如支持丰富多样的调度方法可以满足各种常规及特殊需求灵活的应用方式例如支持任务和调度的多种组合方式支持调度数据的多种存储方式分布式和集群能力Terracotta 收购后在原来功能基础上作了进一步提升。本文暂不讨论该部分内容另外作为 Spring 默认的调度框架Quartz 很容易与 Spring 集成实现灵活可配置的调度功能。 下面是本文中用到的一些专用词汇在此声明 scheduler任务调度器trigger触发器用于定义任务调度时间规则job任务即被调度的任务misfire错过的指本来应该被执行但实际没有被执行的任务调度Quartz 任务调度的基本实现原理 核心元素 Quartz 任务调度的核心元素是 scheduler, trigger 和 job其中 trigger 和 job 是任务调度的元数据 scheduler 是实际执行调度的控制器。 在 Quartz 中trigger 是用于定义调度时间的元素即按照什么时间规则去执行任务。Quartz 中主要提供了四种类型的 triggerSimpleTriggerCronTirggerDateIntervalTrigger和 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 调度线程主要有两个 执行常规调度的线程和执行 misfired trigger 的线程。常规调度线程轮询存储的所有 trigger如果有需要触发的 trigger即到达了下一次触发的时间则从任务执行线程池获取一个空闲线程执行与该 trigger 关联的任务。Misfire 线程是扫描所有的 trigger查看是否有 misfired trigger如果有的话根据 misfire 的策略分别处理。下图描述了这两个线程的基本流程 图 3. Quartz 调度线程流程图 关于 misfired trigger我们在企业应用一节中将进一步描述。 数据存储 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 个基本实例。建议读者阅读并实践这些例子。本文这里只列举一个小的实例介绍基本的开发方法。 准备数据库和 Quartz 用的数据表 本文使用 IBM DB2 数据库将 jdbc 驱动程序 db2jcc.jar 加入到项目中在数据库中创建一个新库 QUARTZDB执行 /quartz-1.8.4/docs/dbTables/tables_db2_v8.sql创建数据表表建好后如下所示图 6. Quartz 数据表 准备配置文件加入到项目中 图 7. 实例配置文件 通过实现 job 接口定义我们自己的任务类如下所示 图 8. 定义任务类 然后实现任务调度的主程序如下所示 本实例中我们利用 DateIntervalTrigger 实现一个每两分钟执行一次的任务调度。 图 9. 实现主程序 完成后项目结构如下所示 图 10. 实例项目结构图 运行程序查看数据库表和运行结果 数据库中QRTZ_TRIGGERS 表中添加了一条 trigger 记录如下所示 图 11. QRTZ_TRIGGERS 表中的记录 QRTZ_JOB_DETAILS 表中添加了一条 job 记录如下所示 图 12. QRTZ_JOB_DETAILES 表中的记录 从运行结果来看任务每两分钟被执行一次 图 13. 运行结果 回页首 企业级开发中的常见应用 在应用 Quartz 进行企业级的开发时有一些问题会经常遇到。本节笔者根据自己在项目开发中的经验介绍企业开发中常见的一些问题以及通常的解决办法。 应用一如何使用不同类型的 Trigger 前面我们提到 Quartz 中四种类型的 TriggerSimpleTriggerCronTirggerDateIntervalTrigger 和 NthIncludedDayTrigger。 SimpleTrigger 一般用于实现每隔一定时间执行任务以及重复多少次如每 2 小时执行一次重复执行 5 次。SimpleTrigger 内部实现机制是通过计算间隔时间来计算下次的执行时间这就导致其不适合调度定时的任务。例如我们想每天的 100AM 执行任务如果使用 SimpleTrigger 的话间隔时间就是一天。注意这里就会有一个问题即当有 misfired 的任务并且恢复执行时该执行时间是随机的取决于何时执行 misfired 的任务例如某天的 300PM。这会导致之后每天的执行时间都会变成 300PM而不是我们原来期望的 100AM。 CronTirgger 类似于 LINUX 上的任务调度命令 crontab即利用一个包含 7 个字段的表达式来表示时间调度方式。例如0 15 10 * * ? * 表示每天的 1015AM 执行任务。对于涉及到星期和月份的调度CronTirgger 是最适合的甚至某些情况下是唯一选择。例如0 10 14 ? 3 WED 表示三月份的每个星期三的下午 1410PM 执行任务。读者可以在具体用到该 trigger 时再详细了解每个字段的含义。 DateIntervalTrigger 是 Quartz 1.7 之后的版本加入的其最适合调度类似每 N1, 2, 3...小时每 N 天每 N 周等的任务。虽然 SimpleTrigger 也能实现类似的任务但是 DateIntervalTrigger 不会受到我们上面说到的 misfired 任务的影响。另外DateIntervalTrigger 也不会受到 DSTDaylight Saving Time 即中国的夏令时调整的影响。笔者就曾经因为该原因将项目中的 SimpleTrigger 改为了 DateIntervalTrigger因为如果使用 SimpleTrigger本来设定的调度时间就会由于 DST 的调整而提前或延迟一个小时而 DateIntervalTrigger 不会受此影响。 NthIncludedDayTrigger 的用途比较简单明确即用于每隔一个周期的第几天调度任务例如每个月的第 3 天执行指定的任务。 除了上面提到的 4 种 TriggerQuartz 中还定义了一个 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 分钟的两次触发作为 misfired job 进行处理。对于 misfired jobQuartz 会查看其 misfire 策略是如何设定的如果是立刻执行则会马上启动一次执行如果是等待下次执行则会忽略错过的任务而等待下次即第 6 分钟触发执行。 读者可以在自己的项目中体会两种任务的区别以及 Quartz 的处理方法根据具体情况选择不同类型的任务。 应用三如何设置 Quartz 的线程池和并发任务 Quartz 中自带了一个线程池的实现SimpleThreadPool。类如其名这只是线程池的一个简单实现没有提供动态自发调整等高级特性。Quartz 提供了一个配置参数org.quartz.threadPool.threadCount可以在初始化时设定线程池的线程数量但是一次设定后不能再修改。假定这个数目是 10则在并发任务达到 10 个以后再有触发的任务就无法被执行了只能等待有空闲线程的时候才能得到执行。因此有些 trigger 就可能被 misfire。但是必须指出一点这个初始线程数并不是越大越好。当并发线程太多时系统整体性能反而会下降因为系统把很多时间花在了线程调度上。根据一般经验这个值在 10 -- 50 比较合适。 对于一些注重性能的线程池来说会根据实际线程使用情况进行动态调整例如初始线程数最大线程数空闲线程数等。读者在应用中如果有更好的线程池则可以在配置文件中通过下面参数替换 SimpleThreadPoolorg.quartz.threadPool.class myapp.GreatThreadPool。 应用四如何处理 Misfired 任务 在 Quartz 应用中misfired job 是经常遇到的情况。一般来说下面这些原因可能造成 misfired job 1系统因为某些原因被重启。在系统关闭到重新启动之间的一段时间里可能有些任务会 被 misfire 2Trigger 被暂停suspend的一段时间里有些任务可能会被 misfire 3线程池中所有线程都被占用导致任务无法被触发执行造成 misfire 4有状态任务在下次触发时间到达时上次执行还没有结束 为了处理 misfired jobQuartz 中为 trigger 定义了处理策略主要有下面两种 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW针对 misfired job 马上执行一次 MISFIRE_INSTRUCTION_DO_NOTHING忽略 misfired job等待下次触发 建议读者在应用开发中将该设置作为可配置选项使得用户可以在使用过程中针对已经添加的 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 是开源的我们可以根据需要进行修改。转载于:https://www.cnblogs.com/Jtianlin/p/4524015.html