Quartz原理解密

Author: Dorae
Date:2018年7月17日15:55:02
轉載請註明出處html

1、quartz概述

quartz是一個用java實現的開源任務調度框架,能夠用來建立簡單或者複雜的任務調度,而且能夠提供許多企業級的功能,好比JTA以及集羣等,是當今比較流行的JAVA任務調度框架。java

1. 能夠用來作什麼

Quartz是一個任務調度框架,當遇到如下問題時:算法

  • 想在每個月25號,自動還款;
  • 想在每一年4月1日給當年本身暗戀的女神發一封匿名賀卡;
  • 想每隔1小時,備份一下本身的各類資料。

那麼總結起來就是,在一個有規律的時間點作一些事情,而且這個規律能夠很是複雜,複雜到了須要一個框架來幫助咱們。Quartz的出現就是爲了解決這個問題,定義一個觸發條件,那麼其負責到了特定的時間點,觸發相應的job幹活。spring

2. 特色

  • 強大的調度功能,例如豐富多樣的調度方法,能夠知足各類常規和特殊需求;
  • 靈活的應用方式,好比支持任務調度和任務的多種組合,支持數據的多種存儲(DB,RAM等;
  • 支持分佈式集羣,在被Terracotta收購以後,在原來基礎上進行了進一步的改造。

2、quartz基本原理

1. 核心元素

Quartz核心要素有Scheduler、Trigger、Job、JobDetail,其中trigger和job、jobDetail爲元數據,而Scheduler爲實際進行調度的控制器。shell

  • Trigger

Trigger用於定義調度任務的時間規則,在Quartz中主要有四種類型的Trigger:SimpleTrigger、CronTrigger、DataIntervalTrigger和NthIncludedTrigger。數據庫

  • Job&Jodetail

Quartz將任務分爲Job、JobDetail兩部分,其中Job用來定義任務的執行邏輯,而JobDetail用來描述Job的定義(例如Job接口的實現類以及其餘相關的靜態信息)。對Quartz而言,主要有兩種類型的Job,StateLessJob、StateFulJob服務器

  • Scheduler

實際執行調度邏輯的控制器,Quartz提供了DirectSchedulerFactory和StdSchedulerFactory等工廠類,用於支持Scheduler相關對象的產生。負載均衡

2. 核心元素間關係

圖 1-1

3. 主要線程

在Quartz中,有兩類線程,也即執行線程和調度線程,其中執行任務的線程一般用一個線程池維護。線程間關係如圖1-2所示。框架

圖 1-2

圖 1-2分佈式

在quartz中,Scheduler調度線程主要有兩個:regular Scheduler Thread(執行常規調度)和Misfire Scheduler Thread(執行錯失的任務)。其中Regular Thread 輪詢Trigger,若是有將要觸發的Trigger,則從任務線程池中獲取一個空閒線程,而後執行與改Trigger關聯的job;Misfire Thraed則是掃描全部的trigger,查看是否有錯失的,若是有的話,根據必定的策略進行處理。

4. 數據存儲

Quartz中的trigger和job須要存儲下來才能被使用。Quartz中有兩種存儲方式:RAMJobStore,JobStoreSupport,其中RAMJobStore是將trigger和job存儲在內存中,而JobStoreSupport是基於jdbc將trigger和job存儲到數據庫中。RAMJobStore的存取速度很是快,可是因爲其在系統被中止後全部的數據都會丟失,因此在集羣應用中,必須使用JobStoreSupport。其中表結構如表1-1所示。

表 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

3、quartz集羣原理

一個Quartz集羣中的每一個節點是一個獨立的Quartz應用,它又管理着其餘的節點。這就意味着你必須對每一個節點分別啓動或中止。Quartz集羣中,獨立的Quartz節點並不與另外一其的節點或是管理節點通訊,而是經過相同的數據庫表來感知到另外一Quartz應用的,如圖1-3所示。

圖 1-3

4、quartz主要流程

1. 啓動流程

若quartz是配置在spring中,當服務器啓動時,就會裝載相關的bean。SchedulerFactoryBean實現了InitializingBean接口,所以在初始化bean的時候,會執行afterPropertiesSet方法,該方法將會調用SchedulerFactory(DirectSchedulerFactory 或者 StdSchedulerFactory,一般用StdSchedulerFactory)建立Scheduler。SchedulerFactory在建立quartzScheduler的過程當中,將會讀取配置參數,初始化各個組件,關鍵組件以下:

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

  2. JobStore:分爲存儲在內存的RAMJobStore和存儲在數據庫的JobStoreSupport(包括JobStoreTX和JobStoreCMT兩種實現,JobStoreCMT是依賴於容器來進行事務的管理,而JobStoreTX是本身管理事務),若要使用集羣要使用JobStoreSupport的方式;

  3. QuartzSchedulerThread:用來進行任務調度的線程,在初始化的時候paused=true,halted=false,雖然線程開始運行了,可是paused=true,線程會一直等待,直到start方法將paused置爲false;

另外,SchedulerFactoryBean還實現了SmartLifeCycle接口,所以初始化完成後,會執行start()方法,該方法將主要會執行如下的幾個動做:

  1. 建立ClusterManager線程並啓動線程:該線程用來進行集羣故障檢測和處理,將在下文詳細討論;
  2. 建立MisfireHandler線程並啓動線程:該線程用來進行misfire任務的處理,將在下文詳細討論;
  3. 置QuartzSchedulerThread的paused=false,調度線程才真正開始調度;

Quartz的整個啓動流程如圖1-4所示。

圖 1-4

2. QuartzSchedulerThread線程

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. 先獲取線程池中的可用線程數量(若沒有可用的會阻塞,直到有可用的);
  2. 獲取30m內要執行的trigger(即acquireNextTriggers): 獲取trigger的鎖,經過select …for update方式實現;獲取30m內(可配置)要執行的triggers(須要保證集羣節點的時間一致),若@ConcurrentExectionDisallowed且列表存在該條trigger則跳過,不然更新trigger狀態爲ACQUIRED(剛開始爲WAITING);插入firedTrigger表,狀態爲ACQUIRED;(注意:在RAMJobStore中,有個timeTriggers,排序方式是按觸發時間nextFireTime排的;JobStoreSupport從數據庫取出triggers時是按照nextFireTime排序);
  3. 等待直到獲取的trigger中最早執行的trigger在2ms內;
  4. triggersFired:
    1. 更新firedTrigger的status=EXECUTING;
    2. 更新trigger下一次觸發的時間;
    3. 更新trigger的狀態:無狀態的trigger->WAITING,有狀態的trigger->BLOCKED,若nextFireTime==null ->COMPLETE;
    4. commit connection,釋放鎖;
  5. 針對每一個要執行的trigger,建立JobRunShell,並放入線程池執行:
    1. execute:執行job
    2. 獲取TRIGGER_ACCESS鎖
    3. 如果有狀態的job:更新trigger狀態:BLOCKED->WAITING,PAUSED_BLOCKED->BLOCKED
    4. 若@PersistJobDataAfterExecution,則updateJobData
    5. 刪除firedTrigger
    6. commit connection,釋放鎖

調度線程的執行流程如圖1-5所示。

圖 1-5

調度過程當中Trigger狀態變化如圖1-6所示。

圖 1-6

3. MisfireHandler線程

下面這些緣由可能形成 misfired job:

  1. 系統由於某些緣由被重啓。在系統關閉到從新啓動之間的一段時間裏,可能有些任務會被 misfire;
  2. Trigger 被暫停(suspend)的一段時間裏,有些任務可能會被 misfire;
  3. 線程池中全部線程都被佔用,致使任務沒法被觸發執行,形成 misfire;
  4. 有狀態任務在下次觸發時間到達時,上次執行尚未結束;爲了處理 misfired job,Quartz 中爲 trigger 定義了處理策略,主要有下面兩種:
    • MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:針對 misfired job 立刻執行一次;
    • MISFIRE_INSTRUCTION_DO_NOTHING:忽略 misfired job,等待下次觸發;默認是MISFIRE_INSTRUCTION_SMART_POLICY,該策略在CronTrigger中=MISFIRE_INSTRUCTION_FIRE_ONCE_NOW線程默認1分鐘執行一次;在一個事務中,默認一次最多recovery 20個;

執行流程:

  1. 若配置(默認爲true,可配置)成獲取鎖前先檢查是否有須要recovery的trigger,先獲取misfireCount;
  2. 獲取TRIGGER_ACCESS鎖;
  3. hasMisfiredTriggersInState:獲取misfired的trigger,默認一個事務裏只能最大20個misfired trigger(可配置),misfired判斷依據:status=waiting,next_fire_time < current_time-misfirethreshold(可配置,默認1min)
  4. notifyTriggerListenersMisfired
  5. updateAfterMisfire:獲取misfire策略(默認是MISFIRE_INSTRUCTION_SMART_POLICY,該策略在CronTrigger中=MISFIRE_INSTRUCTION_FIRE_ONCE_NOW),根據策略更新nextFireTime;
  6. 將nextFireTime等更新到trigger表;
  7. commit connection,釋放鎖8.若是還有更多的misfired,sleep短暫時間(爲了集羣負載均衡),不然sleep misfirethreshold時間,後繼續輪詢;

misfireHandler線程執行流程如圖1-7所示:

圖 1-7

4. ClusterManager集羣管理線程

初始化:

failedInstance=failed+self+firedTrigger表中的schedulerName在scheduler_state表中找不到的(孤兒)

線程執行:

每一個服務器會定時(org.quartz.jobStore.clusterCheckinInterval這個時間)更新SCHEDULER_STATE表的LAST_CHECKIN_TIME,若這個字段遠遠超出了該更新的時間,則認爲該服務器實例掛了;

注意:每一個服務器實例有惟一的id,若配置爲AUTO,則爲hostname+current_time

線程執行的具體流程:

  1. 檢查是否有超時的實例failedInstances;
  2. 更新該服務器實例的LAST_CHECKIN_TIME; 如有超時的實例:
  3. 獲取STATE_ACCESS鎖;
  4. 獲取超時的實例failedInstances;
  5. 獲取TRIGGER_ACCESS鎖;
  6. clusterRecover:
    • 針對每一個failedInstances,經過instanceId獲取每一個實例的firedTriggers;
    • 針對每一個firedTrigger:
      • 更新trigger狀態:
        • BLOCKED->WAITING
        • PAUSED_BLOCKED->PAUSED
        • ACQUIRED->WAITING
      • 若firedTrigger不是ACQUIRED狀態(在執行狀態),且jobRequestRecovery=true: 建立一個SimpleTrigger,存儲到trigger表,status=waiting,MISFIRE_INSTR=MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY.
      • 刪除firedTrigger

clusterManager線程執行時序圖如圖1-8所示:

圖 1-8

5、注意問題

  1. 時間同步問題

Quartz實際並不關心你是在相同仍是不一樣的機器上運行節點。當集羣放置在不一樣的機器上時,稱之爲水平集羣。節點跑在同一臺機器上時,稱之爲垂直集羣。對於垂直集羣,存在着單點故障的問題。這對高可用性的應用來講是沒法接受的,由於一旦機器崩潰了,全部的節點也就被終止了。對於水平集羣,存在着時間同步問題。

節點用時間戳來通知其餘實例它本身的最後檢入時間。假如節點的時鐘被設置爲未來的時間,那麼運行中的Scheduler將再也意識不到那個結點已經宕掉了。另外一方面,若是某個節點的時鐘被設置爲過去的時間,也許另外一節點就會認定那個節點已宕掉並試圖接過它的Job重運行。最簡單的同步計算機時鐘的方式是使用某一個Internet時間服務器(Internet Time Server ITS)。

  1. 節點爭搶Job問題

由於Quartz使用了一個隨機的負載均衡算法,Job以隨機的方式由不一樣的實例執行。Quartz官網上提到當前,還不存在一個方法來指派(釘住) 一個 Job 到集羣中特定的節點。

  1. 從集羣獲取Job列表問題

當前,若是不直接進到數據庫查詢的話,尚未一個簡單的方式來獲得集羣中全部正在執行的Job列表。請求一個Scheduler實例,將只能獲得在那個實例上正運行Job的列表。Quartz官網建議能夠經過寫一些訪問數據庫JDBC代碼來從相應的表中獲取所有的Job信息。

6、參考文獻

  1. www.cnblogs.com/drift-ice/p…
  2. www.cnblogs.com/zhenyuyaodi…
  3. blog.csdn.net/u014427391/…
  4. blog.csdn.net/moon_yang_b…
  5. my.oschina.net/songhongxu/…
  6. wangtianzhi.cn/2016/01/03/…
  7. www.quartz-scheduler.org/documentati…
相關文章
相關標籤/搜索