以下將分別從Quartz架構簡介、集群部署實踐、Quartz監控、集群原理分析詳解Quartz任務調度框架。
Quartz簡介
Quartz是Java領域最著名的開源任務調度工具,是一個任務調度框架,通過觸發器設置作業的定時運行規則,來執行定時任務。其中quartz集群通過故障切換和負載平衡的功能,能給調度器帶來高可用性和伸縮性。
Quartz提供了極為廣泛的特性如持久化任務,集群和分布式任務等。
其特點如下:
- 完全由Java寫成,方便集成(Spring)
- 伸縮性
- 負載均衡
- 高可用性
典型的使用場景,主要用來執行定時任務,例如:
- 定時發送信息
- 定時生成報表
- 自動更新靜態數據
- 自動結賬等等
Quartz架構簡介

Quartz框架主要核心組件包括:
1.Scheduler任務調度
是最核心的概念,需要把JobDetail和Trigger注冊到scheduler中,才可以執行。
工廠模式,組裝各個組件<JOB,Trigger> sched.scheduleJob(job, trigger);
2.Job任務
其實Job是接口,其中只有一個execute方法,我們只需要 implements 此接口,重寫 execute(*) 方法。
3.Trigger觸發器
執行任務的規則;比如每天,每小時等。
一般情況使用SimpleTrigger,和CronTrigger,這些觸發器實現了Trigger接口。或者 ScheduleBuilder 子類 SimpleScheduleBuilder和CronScheduleBuilder。
對於簡單的時間來說,比如每天執行幾次,使用SimpleTrigger。
對於復雜的時間表達式來說,比如每個月15日上午幾點幾分,使用CronTrigger以及CromExpression 類。
4.JobDetail任務細節
任務細節,Quartz執行Job時,需要新建個Job實例,但是不能直接操作Job類,所以通過JobDetail來獲取Job的名稱、描述信息。
調度器作為作業的總指揮,觸發器作為作業的操作者,作業為應用的功能模塊。
Quartz集群部署實踐
Quartz與Spring結合使用,Spring通過提供org.springframework.scheduling.quartz下的封裝類對Quartz支持。
1.Quartz集群部署:

Quartz集群中的每個節點是一個獨立的Quartz應用,它又管理着其他的節點。該集群需要分別對每個節點分別啟動或停止,不像應用服務器的集群,獨立的Quartz節點並不與另一個節點或是管理節點通信。Quartz應用是通過數據庫表來感知到另一應用。只有使用持久的JobStore才能完成Quqrtz集群。
基於Spring的集群配置:
- <!-- 調度工廠 -->
- <bean id="
- quartz
- Scheduler"
- class="org.springframework.scheduling.
- quartz
- .SchedulerFactoryBean">
- <property name="dataSource" ref="dataSource" />
- <property name="
- quartz
- Properties">
- <props>
- <prop key="org.
- quartz
- .scheduler.instanceName">CRMscheduler</prop>
- <prop key="org.quartz.scheduler.instanceId">AUTO</prop>
- <!-- 線程池配置 -->
- <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop>
- <prop key="org.quartz.threadPool.threadCount">20</prop>
- <prop key="org.quartz.threadPool.threadPriority">5</prop>
- <!-- JobStore 配置 -->
- <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
- <!-- 集群配置 -->
- <prop key="org.quartz.jobStore.isClustered">true</prop>
- <prop key="org.quartz.jobStore.clusterCheckinInterval">15000</prop>
- <prop key="org.quartz.jobStore.maxMisfiresToHandleAtATime">1</prop>
- <prop key="org.quartz.jobStore.misfireThreshold">120000</prop>
- <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop>
- </props>
- </property>
- <property name="schedulerName" value="CRMscheduler" />
- <!--必須的,QuartzScheduler 延時啟動,應用啟動完后 QuartzScheduler 再啟動 -->
- <property name="startupDelay" value="30" />
- <property name="applicationContextSchedulerContextKey" value="applicationContextKey" />
- <!--可選,QuartzScheduler 啟動時更新己存在的Job,這樣就不用每次修改targetObject后刪除qrtz_job_details表對應記錄了 -->
- <property name="overwriteExistingJobs" value="true" />
- <!-- 設置自動啟動 -->
- <property name="autoStartup" value="true" />
- <!-- 注冊觸發器 -->
- <property name="triggers">
- <list>
- <ref bean="userSyncScannerTrigger" />
- ......
- </list>
- </property>
- <!-- 注冊jobDetail -->
- <property name="jobDetails">
- <list>
- </list>
- </property>
- <property name="schedulerListeners">
- <list>
- <ref bean="quartzExceptionSchedulerListener" />
- </list>
- </property>
- </bean>
org.quartz.jobStore.class屬性為JobStoreTX,將任務持久化到數據中。因為集群中節點依賴於數據庫來傳播Scheduler實例的狀態,你只能在使用JDBC JobStore時應用Quartz集群。 org.quartz.jobStore.isClustered屬性為true,通知Scheduler實例要它參與到一個集群當中。 org.quartz.jobStore.clusterCheckinInterval屬性定義了Scheduler實例檢入到數據庫中的頻率(單位:毫秒)。Scheduler檢查是否其他的實例到了它們應當檢入的時候未檢入;這能指出一個失敗的Scheduler實例,且當前 Scheduler會以此來接管任何執行失敗並可恢復的Job。通過檢入操作,Scheduler 也會更新自身的狀態記錄。clusterChedkinInterval越小,Scheduler節點檢查失敗的Scheduler實例就越頻繁。默認值是 15000 (即15 秒)。 其余參數在后文將會詳細介紹。
Quartz監控
Quartz實例的監控、操作以及動態部署Trigger.
1.Triggers監控:

2.JobDetails監控:

Quartz集群原理分析
1. Quartz集群數據庫表
Quartz的集群部署方案在架構上是分布式的,沒有負責集中管理的節點,而是利用數據庫鎖的方式來實現集群環境下進行並發控制。BTW,分布式部署時需要保證各個節點的系統時間一致。
Quartz數據庫核心表如下:
- Table NameDescriptionQRTZ_CALENDARS存儲Quartz的Calendar信息
- QRTZ_CRON_TRIGGERS存儲CronTrigger,包括Cron表達式和時區信息
- QRTZ_FIRED_TRIGGERS存儲與已觸發的Trigger相關的狀態信息,以及相聯Job的執行信息
- QRTZ_PAUSED_TRIGGER_GRPS存儲已暫停的Trigger組的信息QRTZ_SCHEDULER_STATE
2. Quartz線程模型
在Quartz中有兩類線程:
- Scheduler調度線程
- 任務執行線程
任務執行線程:Quartz不會在主線程(QuartzSchedulerThread)中處理用戶的Job。
Quartz把線程管理的職責委托給ThreadPool,一般的設置使用SimpleThreadPool。SimpleThreadPool創建了一定數量的WorkerThread實例來使得Job能夠在線程中進行處理。WorkerThread是定義在SimpleThreadPool類中的內部類,它實質上就是一個線程。例如,CRM中配置如下:
<!-- 線程池配置 --> <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop> <prop key="org.quartz.threadPool.threadCount">20</prop> <prop key="org.quartz.threadPool.threadPriority">5</prop>
QuartzSchedulerThread調度主線程:QuartzScheduler被創建時創建一個QuartzSchedulerThread實例。
3.Quartz集群基於數據庫鎖的同步操作流程如下圖所示:

一個調度器實例在執行涉及到分布式問題的數據庫操作前,首先要獲取QUARTZ_LOCKS表中對應的行級鎖,獲取鎖后即可執行其他表中的數據庫操作,隨着操作事務的提交,行級鎖被釋放,供其他調度實例獲取。集群中的每一個調度器實例都遵循這樣一種嚴格的操作規程。
總結一下Quartz集群同步機制:每當要進行與某種業務相關的數據庫操作時,先去QRTZ_LOCKS表中查詢操作相關的業務對象所需要的鎖,在select語句之后加for update來實現。例如,TRIGGER_ACCESS表示對任務觸發器相關的信息進行修改、刪除操作時所需要獲得的鎖。這時,執行查詢這個表數據的SQL形如:
select * from QRTZ_LOCKS t where t.lock_name='TRIGGER_ACCESS' for update
當一個線程使用上述的SQL對表中的數據執行查詢操作時,若查詢結果中包含相關的行,數據庫就對該行進行ROW LOCK;若此時,另外一個線程使用相同的SQL對表的數據進行查詢,由於查詢出的數據行已經被數據庫鎖住了,此時這個線程就只能等待,直到擁有該行鎖的線程完成了相關的業務操作,執行了commit動作后,數據庫才會釋放了相關行的鎖,這個線程才能繼續執行。
通過這樣的機制,在集群環境下,結合悲觀鎖的機制就可以防止一個線程對數據庫數據的操作的結果被另外一個線程所覆蓋,從而可以避免一些難以覺察的錯誤發生。當然,達到這種效果的前提是需要把Connection設置為手動提交,即autoCommit為false。