译者:Unmi(隔叶黄莺)
http://www.blogjava.net/Unmi/archive/2008/05/10/199692.html
第六章. Job 存储和持久化
Quartz 用 JobStores 对 Job、Trigger、calendar 和 Schduler 数据提供一种存储机制。Scheduler 应用已配置的 JobStore 来存储和获取到部署信息,并决定正被触发执行的 Job 的职责。所有的关于哪个 Job 要执行和以什么时间表来执行他们的信息都来自于 JobStore。本章就来看 Quartz 中可用的各种类型的 JobStore,和如何使用他们,以及哪一个能适应你的需求。
"罗马非一日建成"
道格拉斯.亚当斯,《宇宙环游指南》
一. Job 存储
在前面章节中,我们未曾花过任何时间来讨论 Scheduler 的 Job 和 Trigger 是保存在哪儿的。我们也许已经实现了,然而,当你停止了 Scheduler 后,那些有关哪些 Job 已经运行和哪些 Job 没有运行的信息就会丢失掉。实际上,所有的关于正在运行中的 Job 的信息也被销毁。
当程序被重启后,Trigger 和 Job 的信息被加回去,且所有的一切又都正常了。我们假定,有一个 Job 是安排为 5 PM 执行,然而 Scheduler 在这个时间之前的五分钟(4:55 PM) 时停掉了。如果你在 5:05 PM 时重新启动了 Scheduler 的话将会发生什么事情呢?Scheduler 还会记得要在 5 PM 触发这个 Job 的吗?答案就是看它是依赖于你使用的哪种类型的 JobStore,以及是如何对它配置的。
二. Quartz 中的 Job 存储
Quartz 支持对 Scheduler 信息的几种不同类型的存储机制。在 Quartz 中两种可用的 Job 存储类型是:
· 内存(非持久化) 存储
· 持久化存储
默认时,我们在前面几章的例子中已经使用了内存存储机制。两种类型都是用来服务于相同的目的:存储 Job 信息。然而他们各自是如何运作的,而且他们提供给 Scheduler 的功能是很不一样的。
·JobStore 接口
Quartz 为所有类型的 Job 存储提供了一个接口。这个接口位于 org.quartz.spi 包中,叫做 JobStore。所有的 Job 存储机制,不管是在哪里或是如何存储他们的信息的,都必须实现这个接口。
JobStore 可以列出太多的方法来,但是 JobStore 接口的 API 可归纳为下面几类:
·Job 相关的 API
·Trigger 相关的 API
·Calendar 相关的 API
·Scheduler 相关的 API
Quartz 的使用者几乎从不访问或是查看实现了 JobStore 接口的具体类;他们被 Quartz Scheduler 在运行期间内部使用来获取 Job 和 Trigger 信息。不过很值得练习一下,使你自己能熟悉每一种类型,这样你就能更好的理解这些为你所提供的存储机制,并有助于你在 Quartz 应用中选择一个正确的类型。
三. 使用内存来存储 Scheduler 信息
Quartz 直接可用的配置就是把 Job 和 Trigger 信息存储在内存中的。这个解释了为什么,对于前面章节中的例子,每次我们重启了 Quartz 应用程序后,Scheduler 的状态,包括 Job 和 Trigger 信息都丢失了。每回 Java 虚拟机(JVM) 关闭之后,它所占用的内存就释放回给了操作系统,因此任何关于 Job 和 Trigger 的信息都随 JVM 而丢失。
Quartz 的内存 Job 存储的能力是由一个叫做 org.quartz.simple.RAMJobStore 类提供了,当如我们所说,它实现了 JobStore 接口的。RAMJobStore 是 Quartz 的开箱即用的解决方案。对此,我们的意思是说,除非你改变了配置,否则在任何 Quartz 应用中都将使用 RAMJobStore。相比于其他的,使用这种 JobStore 可带来几个好处。
首先,RAMJobStore 是配置最简单的 JobStore:已给你配置好了的。当你下载并安装 Quartz 后,就已为你配置了使用 RAMJobStore 作为存储机制。你能在默认的 quartz.properties 文件中看到这个,如代码 6.1 所示。
代码 6.1. 没有其他配置时默认的 quartz.properties 文件
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
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
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
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
org.quartz.threadPool.threadCount = 10
org.quartz.threadPool.threadPriority = 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
代码 6.1 中显示了包含在 Quartz 二进制包中的默认的 quartz.properties 文件。当你没在你自己的程序中引入一个 quartz.properties 文件,这个属性文件就会得到使用。你可以从默认的 quartz.properties 文件的最后一行看到,RAMJobStore 是名为 org.quartz.jobStore.class 的配置属性的默认值。甚至是未在 quartz.propterties 中设置 org.quartz.jobStore.class 属性时,RAMJobStore 也是默认所用的 JobStore。这是硬编码到 Scheduler 工厂初始化程序中的。
另一使用 RAMJobStore 是优点是它的速度。因为所有的 Scheduler 信息都保存在计算机内存中,访问这些数据随着电脑而变快。这儿不存在进程外的调用,没有数据库连,仅仅是原始而简单的内存访问。再也找不到比这更快的方式了。
·RAMJobStore 的 Job 易失性
你也许还记得在第四章,"部署 Job" 中讲过,Job 可以配置一个易失性属性。当这个易失性属性设置为 false,Job 将会在应用关闭之间持久化下来。这个属性对于用 RAMJobStore 时是不起作用的;那一行为是显式的为持久性的 JobStore 所保留的。
·配置 RAMJobStore
配置你的 Quartz 应用来使用 RAMJobStore 是非常简单的。假如你正用一个定制的 quartz.properties 文件,而不是来自于 Quartz JAR 文件中的,那么加上这行到你的属性文件中即可:
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
这就你要用 RAMJobStore 所要做的事情,正如我们所说的,没有比这更简单的了。
·加载 Job 到 RAMJobStore 中
既然 RAMJobStore 的目的是存储 Scheduler 信息,那么那些信息一开始是如何被加载到内存中的呢?你可以用两种方式加载 Job 信息。首先,你能硬编码你的 Job、Trigger、calendar 和 listener 到你的代码中。如第三章,"Hello,Quarz",第四章,"部署 Job" 所指出的,然而,这总是一件很危险的事情,因为这对于维护来说将是个梦魇。任何的改变,即使是微不足道的,都要修改代码然后重新编译。甚至是只改变触发的时间,也要改代码然后重编译。这没什么大不了的,你说呢?那也许对于小小的程序是这样的,但对于有大量的 Job 和 Trigger 的程序却成了一个大问题。
第二种途径是使用 JobInitializationPlugin,这会在第八章,"使用 Quartz 插件" 中详细讨论。这个 Quartz 插件使用一个 XML 文件来加载 Job、Trigger、Calendar 和其他你需要加载的东西。这种方式的优点是,当有改变时只需要对这个 XML 文件作改动,不用改代码,不用重编译,仅用一个文本编辑器。阅读第八章可获得关于 Quartz 插件更多的信息。
·RAMJobStore 的缺点
你要问了,"RAMJobStore 不能全是正面的,对吗?"。没错,确实如此。我们前面提到几个使用 RAMJobStore 的优点。现在,让我们来谈谈它的一个负面的地方:因为计算机的内存是易失性的,当你的 Quartz 程序被停止了,它会把内存释放回操作系统,当然了,伴随着存储在所释放内存里的别的内容就是这些部署信息了。
假如你的程序的 Scheduler 信息需要在程序重启之间能保持着,那么你需要看看持久性的 JobStore 了。
四. 使用持久性的 JobStore
在很多方面,JobStore 有用内存来存储的,还有些使用某种能长期持久的方式来共享相拟的特征。这不该有什么惊奇的,因为他们都服务于同一目的。
和 RAMJobStore 一样,特久性的 JobStore 有优点也有其缺点。在你选择持久性的 JobStore 之前应该认真理解其利与弊。这节就来解释它们的区别,以及在什么情况下你会希望使用持久性的 JobStore。
目前,Quartz 提供了两种类型的持久性 JobStore,每一种类型都有其独特的持久化机制。
持久性 JobStore = JDBC + 关系型数据库
尽管有几种不同的持久化机制可被 Quartz 用于持久化 Scheduler 信息,Quartz 依赖于一个关系型数据库管理系统(RDMS) 来持久化存储。假如你想用某种别的而不是数据库来持久化存储,那么你必须通过实现 JobStore 接口自己构建它。假定你想用文件系统来持久化存储。你可以创建一个类,这个类要实现 JobStore 接口,在本章中,当我们说 "持久化",我们隐式的是说用 JDBC 来持久化 Scheduler 状态到数据库中
Quartz 所带的所有的持久化的 JobStore 都扩展自 org.quartz.impl.jdbcjobstore.JobStoreSupport 类。
·JobStoreSupport 类
JobStoreSupport 是个抽象类,并实现了 JobStore 接口,在此章前面就讨论过的。它为所有基于 JDBC 的 JobStore 提供了基本的功能。图 6.1 显示了 JobStore 类型的层次关系。
图 6.1. JobStore 类型层次
如图 6.1 所描绘的,JobStoreSupport 实现了 JobStore 接口,是作为 Quartz 提供的两个具体的持久性 JobStore 类的基类。
JobStoreSupport 本该名之为 JDBCJobStoreSupport
作为这个类的一个更好的名字本应该是 JDBCJobStoreSupport,因为这个类专门是为基于 JDBC 存储方案而设置的,然而,这个名并没有减损到它为持久性 JobStore 所提供的功能。
因为 JobStoreSupport 类是抽象的,因此 Quartz 提供了两种不同类型的具体化的 JobStore,每一个设计为针对特定的数据库环境和配置:
·org.quartz.impl.jdbcjobstore.JobStoreTX
·org.quartz.impl.jdbcjobstore.JobStoreCMT
这两个持久性 JobStore 前面简单讨论过。但现在,我们来讨论两个版本所需要的数据库。
五. 为 Job 存储使用数据库
Quartz 中的持久性 JobStore 有时候就是指 JDBC JobStore,因为他们基本是依赖于一个 JDBC 驱动和一个关系型数据库通信。持久性 JobStore 会用到许多的 JDBC 特性,包括支持事特,锁定和隔离级别,只列了这几个特性罢。
要是我的数据库不支持 JDBC 呢?
假如你的数据库不支持 JDBC,那你肯定是碰到什么问题了。并不能全归咎于你运气不好,只是在这之前你还有很多工作要做。你最好是切换到某一种支持的数据库平台上来。假如你的数据库不支持 JDBC,你将需要创建一个新的实现(实现了 JobStore 接口)。你也许想检查一下 Quartz 用户论坛里的用户,看谁是否已经做了这样的工作,并是否愿意共享他们的代码或者至少告诉你实现的方法。
·持久性 JobStore 所支持的数据库
Quartz 中的持久性 JobStore 被设计能与如下数据库平台一同使用:
·Oracle
·MySQL
·MS SQL Server 2000
·HSQLDB
·PostgreSQL
·DB2
·Cloudscape/Derby
·Pointbase
·Informix
·Firebird
·大多数别的有完全 JDBC 兼容性驱动的 RDBMS
·独立环境中的持久性存储
JobStoreTX 类设计为用于独立环境中。这里的 "独立",我们是指这样一个环境,在其中不存在与应用容器的事物集成。这里并不意味着你不能在一个容器中使用 JobStoreTX,只不过,它不是设计来让它的事特受容器管理。区别就在于 Quartz 的事物是否要参与到容器的事物中去。
·程序容器中的持久性存储
JobStoreCMT 类设计为当你想要程序容器来为你的 JobStore 管理事物时,并且那些事物要参与到容器管理的事物边界时使用。它的名字明显是来源于容器管理的事物(Container Managed Transactions (CMT))。
六. 创建 Quartz 数据库结构
JobStore 是基于 JDBC 的,它需要一个数据用于 Scheduler 信息的持久化。Quartz 需要创建 12 张数据库表。表的名字和描述在表 6.1 中列出。
表 6.1. Quartz 需要下列表用于所有的 JDBC 的持久性 JobStore
| 表名 | 描述 |
| QRTZ_CALENDARS | 以 Blob 类型存储 Quartz 的 Calendar 信息 |
| QRTZ_CRON_TRIGGERS | 存储 Cron Trigger,包括 Cron 表达式和时区信息 |
| QRTZ_FIRED_TRIGGERS | 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 |
| QRTZ_PAUSED_TRIGGER_GRPS | 存储已暂停的 Trigger 组的信息 |
| QRTZ_SCHEDULER_STATE | 存储少量的有关 Scheduler 的状态信息,和别的 Scheduler 实例(假如是用于一个集群中) |
| QRTZ_LOCKS | 存储程序的非观锁的信息(假如使用了悲观锁) |
| QRTZ_JOB_DETAILS | 存储每一个已配置的 Job 的详细信息 |
| QRTZ_JOB_LISTENERS | 存储有关已配置的 JobListener 的信息 |
| QRTZ_SIMPLE_TRIGGERS | 存储简单的 Trigger,包括重复次数,间隔,以及已触的次数 |
| QRTZ_BLOG_TRIGGERS | Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候)
|
| QRTZ_TRIGGER_LISTENERS | 存储已配置的 TriggerListener 的信息
|
| QRTZ_TRIGGERS | 存储已配置的 Trigger 的信息
|
表 6.1 中,所有的表都是以前缀 QRTZ_ 开始。这是默认的,但是你可以通过在 quartz.properties 文件中提供一个替代的前缀来改变它。如果你对不同的 Scheduler 实例使用了多套的表,那么改变这个前缀则是必须的。这在你需要用到多个非集群的 Scheduler,但只想用一个单独的数据库实例时也是要做的。
·安装 Quartz 数据库表
Quartz 包括了所有被支持的数据库平台的 SQL 脚本。你能在 <quartz_home>/docs/dbTables 目录下找到那些 SQL 脚本,这里的 <quartz_home> 是解压 Quartz 分发包后的目录。
大约有 18 种不同的数据库平台的脚本。这差不多能覆盖到你所能想出来的任何数据库。假如你的不在其列,你可以使用其中一个已存在的脚本,略做修改以适应你的数据库平台。
要安装必须的数据库表,先打开那个专为你的数据库平台制作的 .sql 文件并用你喜爱的查询工具执行其中的命令。比如说是 MS SQL Server,你需要用数据库所带的查询分析器(Quary Analyzer) 运行文件 tables_sqlServer.sql 中的命令。SQL 命令不负责创建数据库。你还要特别留意 SQL 文件最前面的注释。通常,在运行命令之前都必须执行几条指令。例如,还是 MS SQL Server 的 SQL 文件,你需要修改文件顶端的这条命令,填入数据库名称,在你刚创建它的时候还是一个空数据库。
USE [enter_db_name_here]
SQL 文件创建了必须的表结构,还给表加上了基本的约束和索引。在本章后面,我们会讨论如何通过对表结构做些额外的变动来改进性能。
七. 使用 JobStoreTX
我们首先要讨论的持久性 JobStore 是 JobStoreTX。名字中的 "TX" 代表着 "事物"。我们在前面提过,JobStoreTX 是设计用于想要 Quartz 来管理事物的环境中。例如,假如你正构建一个 J2EE 应用,并且不使用到应用服务器,如 WebLogic 或者 JBoss 等,那么 JobStoreTX 会是持久性 JobStore 正确的选择。
在之前章节中,我们看到配置 RAMJobStore 是多么的容易。我们提到 RAMJobStore 的其中一个优点就是易于配置。我们已经讨论过让数据库准备就绪该做的事情;现在我们讲述使 Quartz 应用支持 JDBC JobStore 需要对它配置些什么。
·配置 JobStoreTX
要告诉 Quartz 运行环境你想使用一个别的 JobStore 而不是默认的 RAMJobStore,你必须配置几个属性。配置它们的顺序无关紧要,只要保证在第一次运行程序之前都做了设置。
设置 JobStore 属性
欲告知 Scheduler 应该使用 JobStoreTX,你必须加上下面一行到 quartz.properties 文件中:
org.quartz.jobStore.class = org.quartz.ompl.jdbcjobstore.JobStoreTX
在切换到 JobStoreTX 时确保移动了 RAMJobStore 行(假如存在)。
配置驱动代理
JDBC API 依赖于专属于某个数据库平台的 JDBC 驱动,同样的,Quartz 依赖于某个 DriverDelegate 来与给定数据库进行通信。顾名思义,从 Scheduler 通过 JobStore 对数据库的调用是委托给一个预配置的 DriverDelegate 实例。这个代理承担起所有与 JDBC driver 也就是数据库的通信。
所有的 DriverDelegate 类都继承自 org.quartz.impl.jdbcjobstore.StdDriverDelegate 类。StdDriverDelegte 只有所有代理可用的,平台无关性的基本功能。然而,在不同的数据库平台间还是存在太多的差异,因此需要为某个平台创建特定的代理。表 6.2 列出特定的代理。
表 6.2. 你必须为你的平台配置其中一个 DriverDelegate
| 数据库平台 | Quartz 代理类 |
| Cloudscape/Derby | org.quartz.impl.jdbcjobstore.CloudscapeDelegate |
| DB2 (version 6.x) | org.quartz.impl.jdbcjobstore.DB2v6Delegate
|
| DB2 (version 7.x) | org.quartz.impl.jdbcjobstore.DB2v7Delegate |
| DB2 (version 8.x) | org.quartz.impl.jdbcjobstore.DB2v8Delegate |
| HSQLDB | org.quartz.impl.jdbcjobstore.PostgreSQLDelegate |
| MS SQL Server | org.quartz.impl.jdbcjobstore.MSSQLDelegate |
| Pointbase | org.quartz.impl.jdbcjobstore.PointbaseDelegate |
| PostgreSQL | org.quartz.impl.jdbcjobstore.PostgreSQLDelegate |
| (WebLogic JDBC Driver) | org.quartz.impl.jdbcjobstore.WebLogicDelegate |
| (WebLogic 8.1 with Oracle) | org.quartz.impl.jdbcjobstore.oracle.weblogic.WebLogicOracleDelegate |
| Oracle | org.quartz.impl.jdbcjobstore.oracle.OracleDelegate |
假如我的数据库平台在表 6.2 中未列出该怎么办?
如果你的 RDBMS 没在上面列出,那么最好的选择就是,直接使用标准的 JDBC 代理 org.quartz.impl.jdbcjobstore.StdDriverDelegate 就能正常的工作。
在你决定好了基于你的数据库平台使用哪个代理,你就需要加入下面的行到 quartz.properties 文件中:
org.quartz.jobStore.driverDelegateClass = <FQN of driver delegate class>
例如,假如你使用 MS SQL Server 作为一你的数据库平台,你就需要加下面这行到属性文件中:
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.MSSQLDelegate
配置数据库表的前缀
前面我们首先讨论 Quartz 要用的数据库表的时候,我们提到所有的表都加有一个前缀 QRTZ_。在某些情况下,你也许需要创建多套的 Quartz 数据库表。在这时候,你就需要改变每一套表的前缀。
表名的前缀配置在 quartz.properties 文件中,使用属性 org.quartz.jobStore.tablePrefix。要改变这一前缀,只要设置这个属性为不同的值:
org.quartz.jobStore.tablePrefix = SCHEDULER2_
确定所有的表名都开始于这一前缀。
数据库表和列的命名
假使你还有所疑惑,数据库表的名字(除却前缀) 和表的列名定义在 org.quartz.impl.jdbcjobstore.Constants 接口中。这个接口为 JobStoreSupport 类所实现,因而那些常量值在 JobStoreTX 或 JobStoreCMT 类中是可用的。
表 6.3 显示了一系列的属性可用于调节 JobStoreTX。
表 6.3. 可用于设置 JobStoreTX 的配置属性
属性 默认值
org.quartz.jobStore.driverDelegateClass
描述:能理解不同数据库系统中某一特定方言的驱动代理
org.quartz.jobStore.dataSource
描述:用于 quartz.properties 中数据源的名称
org.quartz.jobStore.tablePrefix QRTZ_
描述:指定用于 Scheduler 的一套数据库表名的前缀。假如有不同的前缀,Scheduler 就能在同一数据库中使用不同的表。
org.quartz.jobStore.userProperties False
描述:"use properties" 标记指示着持久性 JobStore 所有在 JobDataMap 中的值都是字符串,因此能以 名-值 对的形式存储,而不用让更复杂的对象以序列化的形式存入 BLOB 列中。这样会更方便,因为让你避免了发生于序列化你的非字符串的类到 BLOB 时的有关类版本的问题。
org.quartz.jobStore.misfireThreshold 60000
描述:在 Trigger 被认为是错过触发之前,Scheduler 还容许 Trigger 通过它的下次触发时间的毫秒数(译者注:据原文翻译,真的不好理解,实际效果可参看:
http://www.blogjava.net/Unmi/archive/2007/10/23/153413.html 我在评论中的实验)。默认值(假如你未在配置中存在这一属性条目) 是 60000(60 秒)。这个不仅限于 JDBC-JobStore;它也可作为 RAMJobStore 的参数
org.quartz.jobStore.isClustered False
描述:设置为 true 打开集群特性。如果你有多个 Quartz 实例在用同一套数据库时,这个属性就必须设置为 true。
org.quartz.jobStore.clusterCheckinInterval 15000
描述:设置一个频度(毫秒),用于实例报告给集群中的其他实例。这会影响到侦测失败实例的敏捷度。它只用于设置了 isClustered 为 true 的时候。
org.quartz.jobStore.maxMisfiresToHandleAtATime 20
描述:这是 JobStore 能处理的错过触发的 Trigger 的最大数量。处理太多(超过两打) 很快会导致数据库表被锁定够长的时间,这样就妨碍了触发别的(还未错过触发) trigger 执行的性能。
org.quartz.jobStore.dontSetAutoCommitFalse False
描述:设置这个参数为 true 会告诉 Quartz 从数据源获取的连接后不要调用它的 setAutoCommit(false) 方法。这在少些情况下是有帮助的,比如假如你有这样一个驱动,它会抱怨本来就是关闭的又来调用这个方法。这个属性默认值是 false,因为大多数的驱动都要求调用 setAutoCommit(false)。
org.quartz.jobStore.selectWithLockSQL SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE
描述:这必须是一个从 LOCKS 表查询一行并对这行记录加锁的 SQL 语句。假如未设置,默认值就是 SELECT * FROM {0}LOCKS WHERE LOCK_NAME = ? FOR UPDATE,这能在大部分数据库上工作。{0} 会在运行期间被前面你配置的 TABLE_PREFIX 所替换。
org.quartz.jobStore.txIsolationLevelSerializable False
描述:值为 true 时告知 Quartz(当使用 JobStoreTX 或 CMT) 调用 JDBC 连接的 setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE) 方法。这有助于阻止某些数据库在高负载和长时间事物时锁的超时。
QuartzFigure6_1.jpg