Quartz實踐--misfire錯過觸發時機的處理

1. 引言

要弄清楚做業的misfire,首先須要瞭解幾個重要的概念:html

** 觸發器超時 **:java

舉個例子說明這個概念。好比調度引擎中有5個線程,而後在某天的下午2點 有6個任務須要執行,那麼因爲調度引擎中只有5個線程,因此在2點的時候會有5個任務會按照以前設定的時間正常執行,有1個任務由於沒有線程資源而被延遲執行,這個就叫觸發器超時。下面這些狀況會形成觸發器超時:git

1)系統由於某些緣由被重啓。在系統關閉到從新啓動之間的一段時間裏,可能有些任務會 被 misfire;github

2)Trigger 被暫停(suspend)的一段時間裏,有些任務可能會被 misfire;ide

3)線程池中全部線程都被佔用,致使任務沒法被觸發執行,形成 misfire;ui

4)有狀態任務在下次觸發時間到達時,上次執行尚未結束;spa

** misfireThreshold **:.net

misfireThreshold 即觸發器超時的臨界值,它能夠在quartz.properties文件中配置。misfireThreshold是用來設置調度引擎對觸發器超時的忍耐時間。假設misfireThreshold設置爲6000(單位毫秒),那麼它的意思說當一個觸發器超時時間大於misfireThreshold時,調度器引擎就認爲這個觸發器真正的超時(即Misfires)。換言之,若是一個觸發器超時時間小於設定的misfireThreshold, 那麼調度引擎則不認爲觸發器超時。也就是說這個job並沒發生misfire。線程

quartz.properties中的配置code

#斷定job爲misfire的閾值,這裏設置爲4S
org.quartz.jobStore.misfireThreshold = 4000

那麼,調度器對於觸發器超時可是超時時間小於misfireThreshold 或者 觸發器已經misfire 的兩種狀況是怎麼處理的呢?

2. 調度器怎麼處理超時

2.1 timeout < misfireThreshold

爲了製造超時的現象,實驗時把線程池的大小設定爲1,misfireThreshold設定爲5S。實驗中定義了兩個job,一個是busy job,它在運行期休眠了3S(<misfireThreshold ),另外一個是TimeoutJob。咱們爲TimeoutJob定義了一個timeoutTrigger觸發器,觸發器每隔1S會運行一次TimeoutJob,總共運行7次。經過這樣的設定,在busy job佔用了線程後,timeout job的觸發器已經超時,在3秒的運行期中timeout job觸發器錯過了3次做業運行時機。OK,下面運行代碼看看調度器怎麼處理這個問題。

//BusyJob.java
public class BusyJob implements Job {
	private final Logger logger = LoggerFactory.getLogger(BusyJob.class);

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {

		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String jobName = context.getJobDetail().getKey().getName();

		logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 開始執行");

		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 執行完畢");
	}

}
//TimeoutJob.java
public class TimeoutJob implements Job {
	private final Logger logger = LoggerFactory.getLogger(TimeoutJob.class);

	@Override
	public void execute(JobExecutionContext context) throws JobExecutionException {

		SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

		String jobName = context.getJobDetail().getKey().getName();

		logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 開始執行");

		logger.info("[" + jobName + "]" + " 在  : [" + dateFormat.format(new Date()) + "] 執行完畢");
	}

}
//TimeoutButNotMisfireTest.java
/**
 * 觸發器超時,但沒有misfire
 */
public class TimeoutButNotMisfireTest {

	public static void main(String[] args) throws SchedulerException, InterruptedException {

		Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

		// busy job
		JobDetail busyJob = JobBuilder //
				.newJob(BusyJob.class)//
				.withIdentity("busy job", "group1")//
				.build();

		SimpleTrigger busyTrigger = TriggerBuilder //
				.newTrigger() //
				.withIdentity("busy job trigger", "group1")//
				.startNow() //
				.withPriority(5) // 高優先級
				.withSchedule(SimpleScheduleBuilder.simpleSchedule() //
						.withRepeatCount(0) //
				).build();
		scheduler.scheduleJob(busyJob, busyTrigger);

		// timeout job
		JobDetail timeoutJob = JobBuilder //
				.newJob(TimeoutJob.class)//
				.withIdentity("timeout job", "group2")//
				.build();

		SimpleTrigger timeoutTrigger = TriggerBuilder //
				.newTrigger() //
				.withIdentity("timeout job trigger", "group2")//
				.startNow() //當即觸發
				.withPriority(1) // 低優先級
				.withSchedule(SimpleScheduleBuilder.simpleSchedule() //
						.withIntervalInSeconds(1) //每隔1S觸發一次
						.withRepeatCount(7) // 循環7次
				).build();
		scheduler.scheduleJob(timeoutJob, timeoutTrigger);

		scheduler.start();

		Thread.sleep(20 * 1000);

		scheduler.shutdown(true);

	}
}

運行結果:

INFO  11:31:11,420 com.github.thinwonton.quartz.sample.misfire.BusyJob: [busy job] 在  : [ 11:31:11] 開始執行
 INFO  11:31:14,420 com.github.thinwonton.quartz.sample.misfire.BusyJob: [busy job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 開始執行
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 開始執行
 INFO  11:31:14,422 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:14,423 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 開始執行
 INFO  11:31:14,423 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:14,426 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 開始執行
 INFO  11:31:14,426 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:14] 執行完畢
 INFO  11:31:15,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:15] 開始執行
 INFO  11:31:15,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:15] 執行完畢
 INFO  11:31:16,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:16] 開始執行
 INFO  11:31:16,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:16] 執行完畢
 INFO  11:31:17,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:17] 開始執行
 INFO  11:31:17,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:17] 執行完畢
 INFO  11:31:18,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:18] 開始執行
 INFO  11:31:18,405 com.github.thinwonton.quartz.sample.misfire.TimeoutJob: [timeout job] 在  : [ 11:31:18] 執行完畢

經過觀察運行結果,咱們能夠獲得結論:

超時的觸發器(超時時間小於misfireThreshold)在獲取到運行線程後,將會當即運行前面錯過的做業job,而後按照前面制定的週期性任務正常運行。

2.2 timeout >= misfireThreshold

對於觸發器超時,而且超時時間大於設定的misfireThreshold 這種狀況,調度器引擎爲簡單觸發器SimpleTrigger和表達式CronTrigger提供了多種處理策略,咱們能夠在定義觸發器時指定須要的策略。

2.2.1 對於SimpleTrigger的處理策略

  • MISFIRE_INSTRUCTION_FIRE_NOW : 調度引擎在MisFire的狀況下,將任務(JOB)立刻執行一次。須要注意的是 這個指令一般被用作只執行一次的Triggers,也就是沒有重複的狀況(non-repeating),若是這個Triggers的被安排的執行次數大於0。那麼這個執行與 ** MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT ** 相同。

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT: 調度引擎從新調度該任務,repeat count 保持不變,按照原有制定的執行方案執行repeat count次,可是,若是當前時間,已經晚於 end-time,那麼這個觸發器將不會再被觸發。舉個例子:好比一個觸發器設置的時間是 10:00 執行時間間隔10秒 重複10次。那麼當10:07秒的時候調度引擎能夠執行這個觸發器的任務,而後按照原有制定的時間間隔執行10次。可是若是觸發器設置的執行時間是10:00,結束時間爲10:10,因爲種種緣由致使該觸發器在10:11分才能被調度引擎觸發,這時,觸發器將不會被觸發了。

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 這個策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略相似,惟一的區別就是調度器觸發觸發器的時間不是「如今」 而是下一個 scheduled time。

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT: 這個策略跟上面的策略 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 比較相似,調度引擎從新調度該任務,repeat count 是剩餘應該執行的次數,也就是說原本這個任務應該執行10次,可是已經錯過了3次,那麼這個任務就還會執行7次。

  • MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT: 這個策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT 策略相似,區別就是repeat count 是剩餘應該執行的次數而不是所有的執行次數。好比一個任務應該在2:00執行,repeat count=5,時間間隔5秒, 可是在2:07纔得到執行的機會,那任務不會當即執行,而是按照機會在2點10秒執行。

  • MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY: 這個策略跟上面的 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 策略相似,但這個策略是忽略全部的超時狀態,快速執行以前錯過的次數,而後再按照以前制定的週期觸發觸發器。舉個例子,一個SimpleTrigger 每一個15秒鐘觸發, 可是超時了5分鐘纔得到執行的機會,那麼這個觸發器會被快速連續調用20次, 追上前面落下的執行次數。

2.2.2 對於CronTrigger的處理策略

  • MISFIRE_INSTRUCTION_FIRE_ONCE_NOW: 指示觸發器超時後會被當即安排執行。

  • MISFIRE_INSTRUCTION_DO_NOTHING: 這個策略與策略 MISFIRE_INSTRUCTION_FIRE_ONCE_NOW 正好相反,它不會被當即觸發,而是獲取下一個被觸發的時間,而且若是下一個被觸發的時間超出了end-time 那麼觸發器就不會被執行。

3. 相關資料

代碼:https://git.oschina.net/thinwonton/QuartzDemo

參考博文:http://www.faceye.net/search/110706.html

 

 

概念理解

misfire顧名思義, 就是quartz在應該觸發trigger的時候未能及時將其觸發( 緣由多是線程池沒有線程可用 ), 這將致使trigger的下次觸發時間落在在當前時間以前, 那麼按照正常的quartz調度流程, 該trigger就再沒有機會被調度了. 因爲一個調度器實例在每次調度過程當中都會有必定的睡眠時間, 因此存在一段時間內全部調度器實例都在睡眠, 這也會使trigger不能被及時觸發. 所以調度器須要每隔一段時間( 15s ~ 60s )查看一次各個trigger的nextfiretime( 即下次觸發的時間 ), 檢查出是否有trigger的下次觸發時間落在當前時間以前足夠長的時間, 在這裏系統設定了一個60s的域( misfireThreshold ), 當一個trigger下一次觸發時間早於當前時間60s以外, 調度器斷定該觸發器misfire. 爲了在發現觸發器misfire以後啓動相應的流程恢復trigger至正常的狀態, quartz在trigger中可設置相應的策略. 

misfire策略

CronTrigger

  • withMisfireHandlingInstructionDoNothing 

    不觸發當即執行 
    等待下次Cron觸發頻率到達時刻開始按照Cron頻率依次執行 

    • withMisfireHandlingInstructionIgnoreMisfires 

    以錯過的第一個頻率時間馬上開始執行 
    重作錯過的全部頻率週期後 
    當下一次觸發頻率發生時間大於當前時間後,再按照正常的Cron頻率依次執行 

    • withMisfireHandlingInstructionFireAndProceed 

    以當前時間爲觸發頻率馬上觸發一次執行 
    而後按照Cron頻率依次執行 

SimpleTrigger

  • withMisfireHandlingInstructionFireNow 

    以當前時間爲觸發頻率當即觸發執行 
    執行至FinalTIme的剩餘週期次數 
    以調度或恢復調度的時刻爲基準的週期頻率,FinalTime根據剩餘次數和當前時間計算獲得 
    調整後的FinalTime會略大於根據starttime計算的到的FinalTime值

  • withMisfireHandlingInstructionIgnoreMisfires 

    以錯過的第一個頻率時間馬上開始執行 
    重作錯過的全部頻率週期 
    當下一次觸發頻率發生時間大於當前時間之後,按照Interval的依次執行剩下的頻率 
    共執行RepeatCount+1次 

  • withMisfireHandlingInstructionNextWithExistingCount 

    不觸發當即執行 
    等待下次觸發頻率週期時刻,執行至FinalTime的剩餘週期次數 
    以startTime爲基準計算週期頻率,並獲得FinalTime 
    即便中間出現pause,resume之後保持FinalTime時間不變 

  • withMisfireHandlingInstructionNowWithExistingCount 

    以當前時間爲觸發頻率當即觸發執行 
    執行至FinalTIme的剩餘週期次數 
    以調度或恢復調度的時刻爲基準的週期頻率,FinalTime根據剩餘次數和當前時間計算獲得 
    調整後的FinalTime會略大於根據starttime計算的到的FinalTime值 

  • withMisfireHandlingInstructionNextWithRemainingCount 

    不觸發當即執行 
    等待下次觸發頻率週期時刻,執行至FinalTime的剩餘週期次數 
    以startTime爲基準計算週期頻率,並獲得FinalTime 
    即便中間出現pause,resume之後保持FinalTime時間不變

  • withMisfireHandlingInstructionNowWithRemainingCount 

    以當前時間爲觸發頻率當即觸發執行 
    執行至FinalTIme的剩餘週期次數 
    以調度或恢復調度的時刻爲基準的週期頻率,FinalTime根據剩餘次數和當前時間計算獲得 
    調整後的FinalTime會略大於根據starttime計算的到的FinalTime值

  • MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT 

    此指令致使trigger忘記原始設置的starttime和repeat-count  觸發器的repeat-count將被設置爲剩餘的次數  這樣會致使後面沒法得到原始設定的starttime和repeat-count值 

相關文章
相關標籤/搜索