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

(一)、JobStore接口

??Quartz中的Scheduler調度器、Job任務、Trigger觸發器在前面都已經介紹了,但是未曾提過這些數據是存放在哪里的。要知道,就算不進行持久化,這些信息也應該有個地方進行存儲的。Quartz提供了兩種不同類型的存儲方式,內存存儲和數據庫存儲。這兩種方式都是基于org.quartz.spi.JobStore接口來實現的。

我們先看一下下面這個圖,這是從Eclipse上截取的:

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.StdJDBCDelegate

下面列出一個可用的數據庫代理類表格,方便大家使用,如果表格中沒有列出你想要的代理類,那就使用標準的 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.StdJDBCDelegate

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.StdJDBCDelegate
  • 第三步配置兩個數據源:
    第一個:配置不受應用容器管理的數據源:
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的持久化和配置管理說完了。如果大家有什么問題,請在下方留言,謝謝!
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容