目錄
Trigger 概述與常用觸發器
1、org.quartz.Trigger 是基接口,具有所有觸發器通用的屬性,使用 org.quartz.TriggerBuilder 類實例化實際觸發器。
2、觸發器有一個關聯的 TriggerKey,它應該在單個 Scheduler 中唯一標識它們。
3、多個觸發器可以指向同一個作業(Job),但是一個觸發器只能指向一個作業。
4、觸發器可以通過 JobDataMap 放置屬性將參數/數據傳輸到 Job 。《quartz-scheduler 核心 API》
//設置觸發器的啓動時間爲 30 秒後,然後每10秒執行一次任務.
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.startAt(DateBuilder.futureDate(30, DateBuilder.IntervalUnit.SECOND))
.build();
SimpleTrigger | 簡單觸發器,在一個給定的時間時刻,並有選擇地以指定的間隔重複。使用 SimpleScheduleBuilder 構建 |
DailyTimeIntervalTrigger | 每日間隔時間觸發器,根據每天的重複時間間隔觸發。使用 DailyTimeIntervalScheduleBuilder 構建。 |
CalendarIntervalTrigger | 日曆觸發器,以每 N 個日曆時間單位觸發一次。使用 CalendarIntervalScheduleBuilder 構建. |
CronTrigger | cron 表達式觸發器,靈活指定 年月日時分秒星期。使用 CronScheduleBuilder 構建。 |
Trigger 常用屬性
TriggerKey | 標識 trigger 的身份。鍵由名稱和組構成,且名稱在組中必須唯一。TriggerKey(String name, String group),name 不能爲 null,否則拋異常,group 爲 null 時,使用默認值 “DEFAULT”。 |
startTime |
1)設置觸發器開始的時刻,屬性值是 java.util.Date 類型,默認值爲 startTime = new Date(); 2)有些類型的 trigger,會在設置的 startTime 時刻立即觸發,有些類型的 trigger,則會在 startTime 之後纔開始生效。 3)如當前系統時間爲1月3號,設置一個 trigger 在每個月的第5天執行任務,startTime 屬性設置爲 3 月 2 號,則該 trigger 第一次觸發是在3月5號,然後是 4月5號,以此類推。 |
endTime | 設置 trigger 失效的時刻。比如 ”每月第5天執行” 的 trigger,如果其 endTime 是 6 月30 號,則其最後一次執行時間是 6 月 5 號。 |
Trigger 的屬性在構建 trigger 的時候可以通過 TriggerBuilder 設置,比如:
//設置觸發器的啓動時間爲 startTime,失效時間爲 endTime。
//一到啓動時間 SimpleTrigger 就會第一次執行任務,然後間隔 withIntervalInSeconds 繼續執行
//直到失效時間不再執行,觸發器也會被卸載.
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(startTime)
.endAt(endTime)
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(10)
.repeatForever())
.build();
Trigger 優先級 priority
1、如果 trigger 很多,或者 Quartz 線程池的工作線程太少,Quartz 可能沒有足夠的資源同時觸發所有的 trigger,這種情況下可能希望控制哪些 trigger 優先使用 Quartz 的工作線程。
2、Trigger 的 priority 屬性用於設置優先級,比如有 30 個 trigger 需要同時觸發,但只有 15 個工作線程,此時優先級最高的 15 個 trigger 會被優先觸發。如 TriggerBuilder.newTrigger().withPriority(2).xxx;
3、如果沒有爲 trigger 設置優先級(priority ),則默認爲 5;priority 屬性值可以是任意整數,正數、負數都可以。數字越小優先級越高
4、只有同時觸發的 trigger 之間纔會比較優先級,10:59 觸發的 trigger 總是在 11:00 觸發的 trigger 之前執行。
5、如果 trigger 是可恢復的,在恢復後再調度時,優先級與原 trigger 是一樣的。
6、驗證非常簡單,減小 quartz 線程池中的線程數(quartz.properties Quartz Configuration),然後同時執行多個觸發器,觀察設置 priority 屬性的區別即可 :
#quartz 線程池中的線程個數,如 10 個線程表示最多可以同時執行10個任務/作業
org.quartz.threadPool.threadCount: 10
過期觸發策略(misfire Instructions)
1、trigger 的屬性 misfireInstruction:表示如果 scheduler 關閉了,或者 Quartz 線程池中因爲當時沒有可用的線程來執行 job 而錯過觸發時間,此時持久性的 trigger 就會錯過(miss)其觸發時間,即錯過觸發(misfire)。
2、不同類型的 trigger,有不同的 misfire 機制,它們默認都使用“錯過觸發智能策略(misfire_instruction_smart_policy)”,根據 trigger 的類型和配置動態調整行爲。
3、當 scheduler 啓動的時候,查詢所有錯過觸發(misfire) 的持久性 trigger,然後根據它們各自的 misfire 機制更新 trigger 的信息。
4、過期策略都定義爲了常量,彙總如下:
Trigger 接口公共過期策略 | |
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1 | 忽略策略 |
MISFIRE_INSTRUCTION_SMART_POLICY = 0 | 智能策略 |
SimpleTrigger 觸發器過期策略 | |
MISFIRE_INSTRUCTION_FIRE_NOW = 1 | 立即執行策略 |
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT = 2 | 對現有的重新開始計數執行 |
MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_REMAINING_REPEAT_COUNT = 3 | 重新安排,重複計數 |
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_REMAINING_COUNT = 4 | 重新安排下一個,剩餘計數 |
MISFIRE_INSTRUCTION_RESCHEDULE_NEXT_WITH_EXISTING_COUNT = 5 | 重新安排下一步,現有計數 |
CronTrigger 觸發器過期策略 | |
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW = 1 | 現在執行一次 |
MISFIRE_INSTRUCTION_DO_NOTHING = 2 | 什麼都不做 |
MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1 | 忽略策略 |
5、misfire 策略作爲基本調度(simple schedule)的一部分進行配置(通過 XxxSchedulerBuilder 設置),如:
//創建簡單觸發器,每 30 秒觸發一次,使用 MISFIRE_INSTRUCTION_RESCHEDULE_NOW_WITH_EXISTING_REPEAT_COUNT 過期策略
SimpleTrigger trigger = TriggerBuilder.newTrigger()
.startNow()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(30)
.repeatForever()
.withMisfireHandlingInstructionNowWithExistingCount())
.build();
//每月1號晚上 23:30 執行一次。
//monthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int minute) :表示每月 dayOfMonth 號 hour 點 minute 分 0 秒執行一次.
//等價於 cron 表達式:String cronExpression = String.format("0 %d %d %d * ?", minute, hour,dayOfMonth);
//使用過期策略:CronTrigger.MISFIRE_INSTRUCTION_FIRE_ONCE_NOW
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.monthlyOnDayAndHourAndMinute(6, 21, 44)
.withMisfireHandlingInstructionFireAndProceed())
.build();
org.quartz.Calendar 排除日曆
1、org.quartz.Calendar 用於從 trigger 的調度計劃中排除時間段,比如可以創建一個 trigger,每個工作日的上午 9:30 執行,然後增加一個 Calendar,排除掉所有的商業節日。
2、可以在定義和存儲 trigger 的時候與 org.quartz.Calendar 對象進行關聯。任何實現了Calendar接口的可序列化對象都可以作爲Calendar對象。
3、Calendar 排除時間段的單位可以精確到毫秒,使用時必須先實例化,然後通過 addCalendar() 方法註冊到 scheduler。同一個Calendar 實例可用於多個 trigger。
AnnualCalendar | 年日曆,如可用於排除每年同一日期的銀行假日 |
CronCalendar | cron 日曆,用於排除 cron 表達式指定的時間。 |
DailyCalendar | 每日日曆,此日曆不包括每天指定的時間範圍。例如可以使用此日曆不包括每天的營業時間(上午8點至下午5點) 每個 DailyCalendar 只允許指定一個時間範圍,且該時間範圍不能跨越每日界限(即不能指定從晚上8點到早上5點) |
HolidayCalendar | 假日日曆,用於按天排除,如排除每個週末. |
MonthlyCalendar | 月日曆,用於排除一個月中的某些天 |
WeeklyCalendar | 週日歷,用於排除一週中的某些天 |
3、下面以 HolidayCalendar 進行演示:
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.HolidayCalendar;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class HaiJob2Test {
public static void main(String[] args) {
try {
/**1)創建調度器*/
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
/**2)創建任務詳情*/
JobDetail jobDetail = JobBuilder.newJob(HaiJob.class)
.withIdentity("haiJob2", "haiJobGroup")
.build();
/**3)創建假日日曆 HolidayCalendar,精確到天,表示觸發器遇到這一天時,不觸發執行任務(放假).
* HolidayCalendar extends BaseCalendar implements org.quartz.Calendar:假日日曆,全天被排除在日程之外,
* 即調度器遇到這些日期就放假,不執行任務.
* addExcludedDate(Date excludedDate):將給定日期(年月日)添加到排除日期列表中,即使設置了時分秒,也會被強制置爲 0,存放在 TreeSet<Date> 中.
* removeExcludedDate(Date dateToRemove):移除指定假期日曆,會從 TreeSet<Date> 中進行移除.
*/
Date excludedDate1 = new SimpleDateFormat("yyyy-MM-dd").parse("2020-04-04");
Date excludedDate2 = new SimpleDateFormat("yyyy-MM-dd").parse("2020-04-05");
HolidayCalendar holidayCalendar = new HolidayCalendar();
holidayCalendar.removeExcludedDate(null);
holidayCalendar.addExcludedDate(excludedDate1);
holidayCalendar.addExcludedDate(excludedDate2);
/**向調度程序添加(註冊)給定的 "日曆"
* scheduler.addCalendar(String calName, Calendar calendar, boolean replace, boolean updateTriggers)
* calName:假期日曆的名稱,觸發器會根據名稱進行引用、calendar:假期日曆
* replace:表示調度器 scheduler 中如果已經存在同名的日曆是否替換
* updateTriggers:是否更新引用了現有日曆的現有觸發器,以使其基於新觸發器是"正確的"
* scheduler.getCalendar(String calName):根據名稱獲取調度器中註冊好的日曆
* scheduler.getCalendarNames():獲取調度器中註冊的所有日曆的名稱.
*/
scheduler.addCalendar("hc1", holidayCalendar, true, true);
/**
* 4)創建觸發器
* withIdentity(TriggerKey triggerKey):設置觸發器的名稱以及所屬組名稱,同一組內的觸發器名稱必須唯一
* dailyAtHourAndMinute(int hour, int minute):cron 觸發器,在每天的 hour 時 minute 分 0 秒觸發.
* modifiedByCalendar(String calName):按日曆修改觸發器,對假期日曆 hc1 不觸發執行任務。
*/
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.dailyAtHourAndMinute(17, 18))
.modifiedByCalendar("hc1")
.build();
//註冊任務詳情與觸發器,然後啓動調度器
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
}
源碼:https://github.com/wangmaoxiong/quartzapp/tree/master/src/test/java/com/wmx/quartzapp/helloworld
SimpleTrigger 觸發器
1、SimpleTrigger 通常用於在具體的時間點執行一次,或者在具體的時間點執行,並且以指定的間隔重複執行若干次。
2、SimpleTrigger 的屬性包括:開始時間、結束時間、重複次數、重複的時間間隔。
2.1)開始時間(startTime):設置觸發器開始的時刻,屬性值是 java.util.Date 類型,默認值爲 startTime = new Date();
2.2)結束時間(endTime):結束時間屬性值會覆蓋重複次數屬性值。如設置 trigger 在終止時間之前每隔10秒執行一次,則不需要計算開始時間和終止時間之間的重複次數,只需設置終止時間並將重複次數設爲 SimpleTrigger.repeat_indefinitely=-1(當然也可以將重複次數設置爲一個教大的值,並保證該值比trigger在終止時間之前實際觸發的次數要大即可)。
2.3)重複次數(repeatCount):默認是 0、如果是常量 SimpleTrigger.REPEAT_INDEFINITELY = -1,則表示無限期循環.
2.4)重複間隔(interval):默認是 0,單位爲毫秒。注意如果重複間隔爲 0,trigger 將會以重複次數併發執行(或者以scheduler可以處理的近似併發數)。
3、SimpleTrigger 實例通過 TriggerBuilder 設置公共屬性,通過 SimpleScheduleBuilder 設置與 SimpleTrigger 相關的屬性。
4、使用 org.quartz.DateBuilder 可以非常方便地構造基於開始時間(或終止時間)的調度策略。
一:指定開始觸發時間,不重複,到點就會觸發一次:
/**
* startAt:觸發器開始時間,不設置時,默認 startTime = new Date(),則立即會執行.
* dateOf(int hour, int minute, int second) 返回 Date 對象,表示今天(new Date())的 hour時minute分second秒開始.
* withIdentity(TriggerKey triggerKey):設置觸發器的名稱以及所屬組名稱,同一組內的觸發器名稱必須唯一
*/
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(DateBuilder.dateOf(15, 30, 0))
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.build();
1)上面如果沒寫 startAt,則一啓動就會執行,因爲啓動時間默認爲 new Date()
2)dateOf(int hour, int minute, int second) 從源碼可知,它先是 new Date(),然後通過 java.util.Calendar 的 set 方法將時分秒設置爲 hour、minute、second,最後返回 date.
二:從指定時間開始觸發,然後每隔 5 秒執行一次,重複 10 次:
/**
* startAt:觸發器開始時間,不設置時,默認 startTime = new Date(),則立即會執行.
* dateOf(int hour, int minute, int second,int dayOfMonth, int month)
* 表示 month 月 dayOfMonth 日 hour 點 minute 分 second 秒開始觸發
* withIdentity(TriggerKey triggerKey):設置觸發器的名稱以及所屬組名稱,同一組內的觸發器名稱必須唯一
*/
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(DateBuilder.dateOf(15, 42, 0, 6, 4))
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.withRepeatCount(10))
.build();
在 startAt 時刻會執行第一次,然後重複執行了 10 次,所以實際上面總共是執行了 11 次任務.
三:5 分鐘以後開始觸發,每隔 10 分鐘執行一次,直到今天晚上 22:00:
/**
* startAt:觸發器開始時間,不設置時,默認 startTime = new Date(),則立即會執行.
* futureDate(int interval, IntervalUnit unit):表示未來時間,多少時間後,如 5 分鐘後,1小時後,1天后,3個月後等
* withIdentity(TriggerKey triggerKey):設置觸發器的名稱以及所屬組名稱,同一組內的觸發器名稱必須唯一
* repeatForever():方法內部是將循環次數 repeatCount=-1; 即無限循環,直到 endTime 失效時間
*/
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(DateBuilder.futureDate(5, DateBuilder.IntervalUnit.MINUTE))
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInMinutes(10)
.repeatForever())
.endAt(DateBuilder.dateOf(22, 0, 0))
.build();
1)IntervalUnit 是枚舉,有:MILLISECOND, SECOND, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR.
2)futureDate(int interval, IntervalUnit unit) 方法內部也是通過 java.util.Calendar 的 add 方法將指定間隔單位(unit) 加上 interval 值,最後返回 date.
四:當前時間下一個小時的整點觸發,然後每 2 小時重複一次:
/**
* startAt:觸發器開始時間,不設置時,默認 startTime = new Date(),則立即會執行.
* evenHourDateAfterNow:表示當前時間後面最近的整點,如當前時間爲 16:20,則返回 17:00
* evenSecondDate(Date date):也可以返回時間後面的整點,date 爲 null,默認爲 new Date();
* withIdentity(TriggerKey triggerKey):設置觸發器的名稱以及所屬組名稱,同一組內的觸發器名稱必須唯一
* repeatForever():方法內部是將循環次數 repeatCount=-1; 即無限循環.直到 endTime 失效時間
*/
Trigger trigger = TriggerBuilder.newTrigger()
.startAt(DateBuilder.evenHourDateAfterNow())
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInHours(2)
.repeatForever())
.build();
官網源碼:DateBuilder.java ,本節示例源碼:SimpleTriggerTest.java
CronTrigger 觸發器
1、CronTrigger 觸發器使用起來更加靈活,基本可以代替 SimpleTrigger,使用 CronScheduleBuilder 構建。
2、Cron 表達式是一個字符串,用空格隔開,分爲6或7個域,從左到右爲:秒 分 時 月份中的日期 月 星期中的日期 年份。年份可寫可不寫。
3、網上介紹 cron 表達式的文章有很多,使用 在線Cron表達式生成器 可以輕鬆生成 cron 表達式,下面簡單舉幾例:
3.1)"0/3 * * * * ?" : 每 3 秒鐘執行一次
3.2)"40 0/30 * * * ?":每 30 分鐘在第 40 秒時刻執行一次
3.3)"0 0/30 1 * * * ?":在每天凌晨 1 點內,每 30 分鐘在第 0 秒時刻執行一次
3.4)"0 30 10-13 ?* WED,FRI":每週三、週五的10:30、11:30、12:30、13:30 執行一次
3.5)"0 0/30 8-9 5,20 * ?":每月5日、20日8至10點之間每半小時觸發一次,8:00,8:30,9:00,9:30(注意10點不觸發)3.6)星期可以指定爲1到7(1是星期日),或者字符串 SUN,MON,TUE,WED,THU,FRI,SAT
3.7)月份可以指定爲0到11,或者爲 JAN,FEB,MAR,APR,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC。
3.8)"?" 字符用於日期和星期字段,當其中一個有值時,另一個就設置爲 "?"
3.9)"*" 表示任意時刻
4、對於類似“每天上午9:00至10:00之間每5分鐘,下午1:00至晚上10點之間每20分鐘一次”的需求,通常直接創建兩個觸發器運行相同的作業來解決。
5、startTime 、endTime 屬於公共屬性,所有觸發器都可以進行設置。
一:每天上午 8 點至下午 5 點之間,每隔一分鐘觸發一次
//每天上午 8 點至下午 5 點之間,每隔一分鐘觸發一次,默認啓動時不會執行,啓動1分鐘後開始執行第一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/1 8-17 * * ?"))
.build();
二:每週一、三上午 9:30 執行一次:
//每週一、三上午9:30執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.cronSchedule("0 30 9 ? * MON,WED"))
.build();
三:每月初一晚上 23:30 執行一次:
//每月1號晚上 23:30 執行一次。
//monthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int minute) :表示每月 dayOfMonth 號 hour 點 minute 分 0 秒執行一次.
//等價於 cron 表達式:String cronExpression = String.format("0 %d %d %d * ?", minute, hour,dayOfMonth);
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity(TriggerKey.triggerKey("myTrigger", "myTriggerGroup"))
.withSchedule(CronScheduleBuilder.monthlyOnDayAndHourAndMinute(1, 23, 30))
.build();
monthlyOnDayAndHourAndMinute(int dayOfMonth, int hour, int minute) | 每月幾號幾點幾分執行一次,等價: String.format("0 %d %d %d * ?", minute, hour,dayOfMonth); |
dailyAtHourAndMinute(int hour, int minute) | 每天幾點幾分執行一次,等價: String.format("0 %d %d ? * *", minute, hour) |
weeklyOnDayAndHourAndMinute(int dayOfWeek, int hour, int minute) | 每週幾點幾分執行一次,等價: String.format("0 %d %d ? * %d", minute, hour,dayOfWeek); |
CronTrigger 過期觸發策略(misfire Instructions)
本節源碼:CronTriggerTest.java
Quartz 數據持久化
1、org.quartz.spi.JobStore 負責跟蹤調度程序的所有"工作數據",如 jobs,triggers,Calendar 等。
RAMJobStore | 內存存儲。性能最高,程序關閉後,所有調度數據會丟失。切換 RAMJobStore,只需如下設置即可(這也是默認方式): org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore #指定存儲方式爲 RAM 內存存儲 |
JobStoreTX | 數據庫存儲。如果不需要將調度命令(例如添加和刪除triggers)綁定到其他事務,那麼可以通過使用 JobStoreTX 管理事務(這是最常見的選擇)。org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX |
JobStoreCMT | 數據庫存儲。如果需要 Quartz 與其他事務(即J2EE應用程序服務器)一起工作,那麼應該使用 JobStoreCMT,這種情況下,Quartz 將讓應用程序服務器容器管理事務。org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreCMT |
2、quartz 默認就是使用的內存存儲,也就是說當什麼都沒有配置的時候,它就是 RAMJobStore。《quartz-scheduler 定時器概述、核心 API 與 快速入門》中也是內存存儲。
3、《Spring Boot 2.1.3 集成 Quartz 定時器, jdbc 持久化調度信息》中使用 JobStoreTX 將數據持久化到數據庫。
4、需要使用 jdbc 存儲到數據庫中,則可以參考《Spring Boot 2.1.3 集成 Quartz 定時器, jdbc 持久化調度信息》