Author: Dorae
Date:2018年7月17日15:55:02
轉載請註明出處html
quartz是一個用java實現的開源任務調度框架,能夠用來建立簡單或者複雜的任務調度,而且能夠提供許多企業級的功能,好比JTA以及集羣等,是當今比較流行的JAVA任務調度框架。java
Quartz是一個任務調度框架,當遇到如下問題時:算法
那麼總結起來就是,在一個有規律的時間點作一些事情,而且這個規律能夠很是複雜,複雜到了須要一個框架來幫助咱們。Quartz的出現就是爲了解決這個問題,定義一個觸發條件,那麼其負責到了特定的時間點,觸發相應的job幹活。spring
Quartz核心要素有Scheduler、Trigger、Job、JobDetail,其中trigger和job、jobDetail爲元數據,而Scheduler爲實際進行調度的控制器。shell
Trigger用於定義調度任務的時間規則,在Quartz中主要有四種類型的Trigger:SimpleTrigger、CronTrigger、DataIntervalTrigger和NthIncludedTrigger。數據庫
Quartz將任務分爲Job、JobDetail兩部分,其中Job用來定義任務的執行邏輯,而JobDetail用來描述Job的定義(例如Job接口的實現類以及其餘相關的靜態信息)。對Quartz而言,主要有兩種類型的Job,StateLessJob、StateFulJob服務器
實際執行調度邏輯的控制器,Quartz提供了DirectSchedulerFactory和StdSchedulerFactory等工廠類,用於支持Scheduler相關對象的產生。負載均衡
在Quartz中,有兩類線程,也即執行線程和調度線程,其中執行任務的線程一般用一個線程池維護。線程間關係如圖1-2所示。框架
圖 1-2分佈式
在quartz中,Scheduler調度線程主要有兩個:regular Scheduler Thread(執行常規調度)和Misfire Scheduler Thread(執行錯失的任務)。其中Regular Thread 輪詢Trigger,若是有將要觸發的Trigger,則從任務線程池中獲取一個空閒線程,而後執行與改Trigger關聯的job;Misfire Thraed則是掃描全部的trigger,查看是否有錯失的,若是有的話,根據必定的策略進行處理。
Quartz中的trigger和job須要存儲下來才能被使用。Quartz中有兩種存儲方式:RAMJobStore,JobStoreSupport,其中RAMJobStore是將trigger和job存儲在內存中,而JobStoreSupport是基於jdbc將trigger和job存儲到數據庫中。RAMJobStore的存取速度很是快,可是因爲其在系統被中止後全部的數據都會丟失,因此在集羣應用中,必須使用JobStoreSupport。其中表結構如表1-1所示。
Table name | Description |
---|---|
QRTZ_CALENDARS | 存儲Quartz的Calendar信息 |
QRTZ_CRON_TRIGGERS | 存儲CronTrigger,包括Cron表達式和時區信息 |
QRTZ_FIRED_TRIGGERS | 存儲與已觸發的Trigger相關的狀態信息,以及相聯Job的執行信息 |
QRTZ_PAUSED_TRIGGER_GRPS | 存儲已暫停的Trigger組的信息 |
QRTZ_SCHEDULER_STATE | 存儲少許的有關Scheduler的狀態信息,和別的Scheduler實例 |
QRTZ_LOCKS | 存儲程序的悲觀鎖的信息 |
QRTZ_JOB_DETAILS | 存儲每個已配置的Job的詳細信息 |
QRTZ_SIMPLE_TRIGGERS | 存儲簡單的Trigger,包括重複次數、間隔、以及已觸的次數 |
QRTZ_BLOG_TRIGGERS | Trigger做爲Blob類型存儲 |
QRTZ_TRIGGERS | 存儲已配置的Trigger的信息 |
QRTZ_SIMPROP_TRIGGERS |
一個Quartz集羣中的每一個節點是一個獨立的Quartz應用,它又管理着其餘的節點。這就意味着你必須對每一個節點分別啓動或中止。Quartz集羣中,獨立的Quartz節點並不與另外一其的節點或是管理節點通訊,而是經過相同的數據庫表來感知到另外一Quartz應用的,如圖1-3所示。
若quartz是配置在spring中,當服務器啓動時,就會裝載相關的bean。SchedulerFactoryBean實現了InitializingBean接口,所以在初始化bean的時候,會執行afterPropertiesSet方法,該方法將會調用SchedulerFactory(DirectSchedulerFactory 或者 StdSchedulerFactory,一般用StdSchedulerFactory)建立Scheduler。SchedulerFactory在建立quartzScheduler的過程當中,將會讀取配置參數,初始化各個組件,關鍵組件以下:
ThreadPool:通常是使用SimpleThreadPool,SimpleThreadPool建立了必定數量的WorkerThread實例來使得Job可以在線程中進行處理。WorkerThread是定義在SimpleThreadPool類中的內部類,它實質上就是一個線程。在SimpleThreadPool中有三個list:workers-存放池中全部的線程引用,availWorkers-存放全部空閒的線程,busyWorkers-存放全部工做中的線程; 線程池的配置參數以下所示:
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount=3 org.quartz.threadPool.threadPriority=5
JobStore:分爲存儲在內存的RAMJobStore和存儲在數據庫的JobStoreSupport(包括JobStoreTX和JobStoreCMT兩種實現,JobStoreCMT是依賴於容器來進行事務的管理,而JobStoreTX是本身管理事務),若要使用集羣要使用JobStoreSupport的方式;
QuartzSchedulerThread:用來進行任務調度的線程,在初始化的時候paused=true,halted=false,雖然線程開始運行了,可是paused=true,線程會一直等待,直到start方法將paused置爲false;
另外,SchedulerFactoryBean還實現了SmartLifeCycle接口,所以初始化完成後,會執行start()方法,該方法將主要會執行如下的幾個動做:
Quartz的整個啓動流程如圖1-4所示。
QuartzSchedulerThread線程是實際執行任務調度的線程,其中主要代碼以下。
while (!halted.get()) {
int availThreadCount = qsRsrcs.getThreadPool().blockForAvailableThreads();
triggers = qsRsrcs.getJobStore().acquireNextTriggers(now + idleWaitTime,
Math.min(availThreadCount, qsRsrcs.getMaxBatchSize()), qsRsrcs.getBatchTimeWindow());
long triggerTime = triggers.get(0).getNextFireTime().getTime();
long timeUntilTrigger = triggerTime - now;
while (timeUntilTrigger > 2) {
now = System.currentTimeMillis();
timeUntilTrigger = triggerTime - now;
}
List<TriggerFiredResult> bndle = qsRsrcs.getJobStore().triggersFired(triggers);
for (int i = 0; i < res.size(); i++) {
JobRunShell shell = qsRsrcs.getJobRunShellFactory().createJobRunShell(bndle);
shell.initialize(qs);
qsRsrcs.getThreadPool().runInThread(shell);
}
}
複製代碼
調度線程的執行流程如圖1-5所示。
調度過程當中Trigger狀態變化如圖1-6所示。
下面這些緣由可能形成 misfired job:
執行流程:
misfireHandler線程執行流程如圖1-7所示:
初始化:
failedInstance=failed+self+firedTrigger表中的schedulerName在scheduler_state表中找不到的(孤兒)
線程執行:
每一個服務器會定時(org.quartz.jobStore.clusterCheckinInterval這個時間)更新SCHEDULER_STATE表的LAST_CHECKIN_TIME,若這個字段遠遠超出了該更新的時間,則認爲該服務器實例掛了;
注意:每一個服務器實例有惟一的id,若配置爲AUTO,則爲hostname+current_time
線程執行的具體流程:
clusterManager線程執行時序圖如圖1-8所示:
Quartz實際並不關心你是在相同仍是不一樣的機器上運行節點。當集羣放置在不一樣的機器上時,稱之爲水平集羣。節點跑在同一臺機器上時,稱之爲垂直集羣。對於垂直集羣,存在着單點故障的問題。這對高可用性的應用來講是沒法接受的,由於一旦機器崩潰了,全部的節點也就被終止了。對於水平集羣,存在着時間同步問題。
節點用時間戳來通知其餘實例它本身的最後檢入時間。假如節點的時鐘被設置爲未來的時間,那麼運行中的Scheduler將再也意識不到那個結點已經宕掉了。另外一方面,若是某個節點的時鐘被設置爲過去的時間,也許另外一節點就會認定那個節點已宕掉並試圖接過它的Job重運行。最簡單的同步計算機時鐘的方式是使用某一個Internet時間服務器(Internet Time Server ITS)。
由於Quartz使用了一個隨機的負載均衡算法,Job以隨機的方式由不一樣的實例執行。Quartz官網上提到當前,還不存在一個方法來指派(釘住) 一個 Job 到集羣中特定的節點。
當前,若是不直接進到數據庫查詢的話,尚未一個簡單的方式來獲得集羣中全部正在執行的Job列表。請求一個Scheduler實例,將只能獲得在那個實例上正運行Job的列表。Quartz官網建議能夠經過寫一些訪問數據庫JDBC代碼來從相應的表中獲取所有的Job信息。