六、Quartz任务持久化和配置管理

(一)、JobStore接口

  Quartz中的Scheduler调度器、Job任务、Trigger触发器在前面都已经介绍了,但是未曾提过这些数据是存放在哪里的。要知道,就算不进行持久化,这些信息也应该有个地方进行存储的。Quartz提供了两种不同类型的存储方式,内存存储和数据库存储。这两种方式都是基于org.quartz.spi.JobStore接口来实现的。

我们先看一下下面这个图,这是从Eclipse上截取的:

六、Quartz任务持久化和配置管理_第1张图片
clipboard.png
  • org.quartz.spi.JobStore 是任务存储的顶层接口类
  • org.quartz.simpl.RAMJobStore 是内存存储机制实现类
  • org.quartz.impl.jdbcjobstore.JobStoreSupport 是基于JDBC数据库存储的抽象类
  • org.quartz.impl.jdbcjobstore.JobStoreCMT 是受应用容器管理事物的数据库存储实现类
  • org.quartz.impl.jdbcjobstore.JobStoreTX 是不受应用容器事物管理的数据库存储实现类

  org.quartz.spi.JobStore作为任务存储的顶层接口类,他定义了很多的接口方法,总共可归纳为四类,调度器类、任务类、触发器类和之前未提到的Calendar日期这一类,Calendar主要是配合触发器一起设置一些特殊的触发时间而使用的。在项目开发中,我们无需调用JobStore实现类中的方法,但是了解还是很有必要的,因为可以让我们在项目应用中选择更加适合的存储类型。如何框架提供的存储机制不能满足要求,还可以自定义其他的存储方式,比如文件系统存储,如果真这么干,那就需求自己实现JobStore接口,并且实现大约40个接口方法,可以参考RAMJobStore类来看看框架内部具体做了什么再去实现自己的存储类。

(二)、JobStore接口的几种实现类

  接下来我们了解一下上图中提到的几种存储方式。

(1)、使用RAMJobStore内存存储数据

Quartz默认的存储机制就是使用内存进行存储的,我们先看一下Quartz的jar包中的默认配置文件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

  主要看org.quartz.jobStore.class这个属性,属性值org.quartz.simpl.RAMJobStore就是内存存储机制的实现类。如果需要使用别的存储机制,那就将此值替换为别的实现类即可。
  使用内存存储的优点是任务的存储和读取的速度极快,和数据库持久化相比差别还是非常大的,而且框架搭建简单,开箱即用。它的缺点是当Quartz程序或应用程序停止了,伴随在内存中的数据也会被回收,任务等数据就永久丢失了。
  使用内存存储时,注意配置文件中只需要保留基本的线程池配置和jobStore的实现类等几个简单的属性就行。如果使用了实现类中没有的属性,启动的时候会报错,当然错误提示也很明显。这里主要想提醒一下之前已经使用了持久化配置,现在想体验一下内存存储的朋友们。

(2)、使用数据库存储数据

  通过上面的类图可以看出,JobStoreTX和JobStoreCMT都是JobStoreSupport抽象类的实现类,JobStoreSupport是基于JDBC实现了一些基本的功能的抽象类。如果想要自己实现一套关于JDBC存储方式,那么可以继承此抽象类。

我们先看一下Quartz支持哪些数据库:
·Oracle
·MySQL
·Microsoft SQL Server 2000
·HSQLDB
·PostgreSQL
·DB2
·Cloudscape/Derby
·Pointbase
·Informix
·Firebird
。。。等等,总之兼容JDBC驱动的关系型数据库都可以。

  了解了哪些数据库可以使用,接下来就是创建数据库了,Quartz提供了各种数据库的脚本,脚本中有创建表和索引的sql,但是没有创建数据库的sql,需要自己先创建数据库,然后执行创建表和索引的脚本。脚本的创建已经在@一、Quartz集成-下载和安装章节中的第三节讲过,这里不再重复讲述。

下面介绍一下各个表的含义:

表名 含义
QRTZ_CALENDARS 以 Blob 类型存储 Quartz 的 Calendar 信息
QRTZ_CRON_TRIGGERS 存储CronTrigger触发器信息,包括Cron表达式和时区等信息
QRTZ_FIRED_TRIGGERS 存储已触发的Trigger状态信息和关联的Job执行信息
QRTZ_PAUSED_TRIGGER_GRPS 存储已暂停的Trigger组信息
QRTZ_SCHEDULER_STATE 存储有关Scheduler的状态信息
QRTZ_LOCKS 存储程序锁信息
QRTZ_JOB_DETAILS 存储Job的详细信息
QRTZ_JOB_LISTENERS 存储Job配置的JobListener信息
QRTZ_SIMPLE_TRIGGERS 存储SimpleTrigger触发器信息,包括重复次数,间隔等信息
QRTZ_BLOG_TRIGGERS 存储Blob类型的Trigger,一般用于自定义触发器
QRTZ_TRIGGER_LISTENERS 存储已配置的TriggerListener信息
QRTZ_TRIGGERS 存储已配置的Trigger的信息

  表的前缀默认都是QRTZ_ 开始,我们先了解一下这个表前缀有什么用。假设项目中需要有两套调度器实例,我们想分别持久化这两个实例信息,此时就需要两套上面的表。为了区分表名称,就在前面加上表前缀。比如:

  org.quartz.jobStore.tablePrefix=QRTZ1_
  org.quartz.jobStore.tablePrefix=QRTZ2_

  QRTZ1_ 和QRTZ2_分别是两套表的前缀,分别配置在不同的quartz.properties中,然后根据两个配置文件分别初始化调度实例。

数据库创建好了,下面先使用JobStoreTX存储机制,我们接着往下看:

1、JobStoreTX

  TX就是事物的意思,此存储机制用于Quartz独立于应用容器的事物管理,如果是Tomcat容器管理的数据源,那我们定义的事物也不会传播给Quartz框架内部。通俗的讲就是不管我们的Service服务本身业务代码是否执行成功,只要代码中调用了Quartz API的数据库操作,那任务状态就永久持久化了,就算业务代码抛出运行时异常任务状态也不会回滚到之前的状态。

下面介绍一下使用JobStoreTX配置步骤,所有的配置都是在quartz.properties中完成:

  • 第一步配置org.quartz.jobStore.class属性:
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreTX
  • 第二步配置驱动代理,以Mysql为例:
org.quartz.jobStore.driverDelegateClass= org.quartz.impl.jdbcjobstore.MSSQLDelegate

下面列出一个可用的数据库代理类表格,方便大家使用,如果表格中没有列出你想要的代理类,那就使用标准的 JDBC 代理:org.quartz.impl.jdbcjobstore.StdDriverDelegate

数据库平台 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
Oracle org.quartz.impl.jdbcjobstore.oracle.OracleDelegate
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
  • 第三步配置数据源:
org.quartz.jobStore.dataSource=qzDS
org.quartz.dataSource.qzDS.driver= com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL= jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.qzDS.user= root
org.quartz.dataSource.qzDS.password= admin
org.quartz.dataSource.qzDS.maxConnection= 20

  这里要注意两点:
  第一是org.quartz.dataSource.qzDS.URL属性名末尾的URL字符串必须是大写,如果写成org.quartz.dataSource.qzDS.url ,那初始化调度实例时就会报错。

  第二是注意org.quartz.jobStore.dataSource属性,这个属性的意思是给数据源起一个名字。这里属性值配置的是“qzDS”,你也可以配置成别的任意字符串,比如:“abc”,如果真这么做,那就需要将org.quartz.dataSource.qzDS.driver和其他配置的“qzDS”更换为“abc”,配置:

org.quartz.jobStore.dataSource=abc
org.quartz.dataSource.abc.driver= com.mysql.jdbc.Driver
org.quartz.dataSource.abc.URL= jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.abc.user= root
org.quartz.dataSource.abc.password= admin
org.quartz.dataSource.abc.maxConnection= 20

  那么Quartz为什么设计要org.quartz.jobStore.dataSource属性呢?
  这个属性主要的目的就是在同一个数据库中需要使用多套Quartz,一般大家只需要一套数据源就可以完成业务工作,除非有一些特别的需求。比如SaaS模式下可以对不同公司的任务调度进行管理等。

下面提供一个拿去就能用的配置文件,改一下数据源、用户名和密码即可:

org.quartz.scheduler.instanceName=DefaultQuartzScheduler

org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=20
org.quartz.threadPool.threadPriority=5
org.quartz.jobStore.misfireThreshold=60000

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

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

org.quartz.jobStore.tablePrefix=qrtz_
org.quartz.jobStore.dataSource=qzDS

org.quartz.dataSource.qzDS.driver= com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL= jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.qzDS.user= root
org.quartz.dataSource.qzDS.password= admin
org.quartz.dataSource.qzDS.maxConnection= 20
2、JobStoreCMT

  CMT的全称是Container Managed Transactions,表示容器管理事物,也就是让应用容器托管事物。这里假设应用容器是Tomcat,并且项目和Quartz都是使用Tomcat配置的数据源,那么项目和Quartz的代码中就可以共用同一个事物,不管是业务代码还是Quartz内部抛出异常,Service服务内的所有数据操作都会回滚到原始状态。JobStoreCMT和JobStoreTX最大的区别是JobStoreCMT需要配置两个数据源,一个是受应用容器管理的数据源,还有一个是不受应用容器管理的数据源。
  这里需要想一想为什么需要两个数据源?
  我个人的理解是不受应用容器管理的数据源用来由Quartz内部进行"增删改查",假如一个触发器已失效,那么Quartz框架内部就会自动删除这个触发器并提交事物,而无需开发人员的项目代码来处理,全由Quartz内部管理。

下面介绍一下使用JobStoreCMT配置步骤,所有的配置都是在quartz.properties中完成:

  • 第一步配置org.quartz.jobStore.class属性:
org.quartz.jobStore.class= org.quartz.impl.jdbcjobstore.JobStoreCMT
  • 第二步配置驱动代理,以Mysql为例,其它代理类参考上面表格:
org.quartz.jobStore.driverDelegateClass= org.quartz.impl.jdbcjobstore.MSSQLDelegate
  • 第三步配置两个数据源:
    第一个:配置不受应用容器管理的数据源:
org.quartz.jobStore.nonManagedTXDataSource = qzDS
org.quartz.dataSource.qzDS.driver = com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.URL = jdbc:mysql://localhost:3306/testDB
org.quartz.dataSource.qzDS.user = root
org.quartz.dataSource.qzDS.password = admin
org.quartz.dataSource.qzDS.maxConnections = 10

 nonManagedTXDataSource就是非管理事物数据源的意思。

 第二个:配置受应用容器管理的数据源:

org.quartz.dataSource.dataSource=myDS
org.quartz.dataSource.jndiURL = jdbc/mysql
org.quartz.dataSource.myDS.jndiAlwaysLookup = DB_JNDI_ALWAYS_LOOKUP
org.quartz.dataSource.myDS.java.naming.factory.initial = org.apache.naming.java.javaURLContextFactory
org.quartz.dataSource.myDS.java.naming.provider.url = http://localhost:8080
org.quartz.dataSource.myDS.java.naming.security.principal = root
org.quartz.dataSource.myDS.java.naming.security.credentials = admin

  注意:配置之前大家可能需要去了解学习一下JNDI+应用容器(Tomcat等)如何配置数据源,本文就不讲述如何配置了。

下面解释一下受应用容器管理的数据源配置属性的含义:

  • org.quartz.dataSource.NAME.jndiURL
    受应用服务器管理的DataSource的JNDI URL
  • org.quartz.dataSource.NAME.java.naming.factory.initial
    JNDI InitialContextFactory的类名称
  • org.quartz.dataSource.NAME.java.naming.provider.url
    连接到JNDI的URL
  • org.quartz.dataSource.NAME.java.naming.security.principal
    连接到 JNDI 的用户名
  • org.quartz.dataSource.NAME.java.naming.security.credential
    连接到 JNDI 的用户凭证密码
(三)、如何选择使用哪种存储机制?
1、什么情况下使用RAMJobStore内存存储方式呢?

  根据开发中的使用经验,发现有些任务是随着项目启动而启动的,就算项目关闭或系统宕机,那也没关系,因为项目重新启动后此任务又会随之启动。如果项目中只存在这类任务,那么就可以用内存存储。随着项目启动有几种常用的实现方式,第一种是通过实现ServletContextListener监听器接口,然后在接口实现类的contextInitialized()方法中编写启动Job的硬编码;第二种是通过Quartz的XML配置文件启动任务。

2、什么情况下使用JobStoreTX数据库存储方式呢?

  第一篇文章@一、Quartz集成-下载和安装中的配置就用到了JobStoreTX,那个配置文件是我在实际开发中使用的,使用这种存储方式的情况很多。使用这种方式需要注意的是,如果在一个Service服务中需要创建一个Job,那么请把创建Job的代码编写在服务代码的最后面,确保业务代码运行成功并且没有抛异常再去启动Job,如果启动Job失败的时候请抛出一个运行时异常使业务代码进行回滚。
例子:

@Transactional
public void demoService(TaskStore taskStore) {
    // 先执行插入业务操作
    taskStoreService.insert(taskStore);

    // 再执行更新业务操作
    taskDetailService.update(taskDetail);

    // 最后启动定时任务
    QuartzUtils.addJob("testName", DemoJob.class, "0 * * * * * ?");
}

  注意例子中的addJob()方法中捕获了异常后进行重新封装再抛出运行时异常的,目的是Quartz内部错误时确保业务代码回滚。

3、什么情况下使用JobStoreCMT数据库存储方式呢?

  JobStoreCMT和JobStoreTX的区别前文已经介绍了,在实际开发的过程中我还没有在项目中使用过此种方式。一般情况下都是使用的JobStoreTX。如果大家的项目中有着严格的事物管理,那么建议使用JobStoreCMT存储方式。

到此,Quartz的持久化和配置管理说完了。如果大家有什么问题,请在下方留言,谢谢!

你可能感兴趣的:(六、Quartz任务持久化和配置管理)