任务调度之Quartz1
目標
1、了解任務調度的應用場景和Quartz的基本特性
2、掌握Quartz Java編程和Spring集成的使用
3、掌握Quartz動態調度和集群部署的實現
4、理解Quartz原理與線程模型
今天你將學到:
1、除了下視頻下圖片,定時任務還可以干什么?
2、當我們在用Spring Task的時候,我們在用什么?
3、節假日購買理財不計息,怎么實現?
4、任務跑完給管理員發一條短信,怎么實現?
5、明明配置了線程池,怎么就變成單線程了?
6、怎么kill一個進程里面的一個任務?
7、Quartz的幕后角色:包工頭、工人、項目經理
8、當我想要任務重復執行的時候,為什么沒有重復執行?
內容定位
適合沒有用過Quartz或者只會Quartz基本配置的同學
說明:基于最新穩定版本2.3.0
漫談任務調度
什么時候需要任務調度?
任務調度的背景
在業務系統中有很多這樣的場景:
1、賬單日或者還款日上午10 點,給每個信用卡客戶發送賬單通知,還款通知。如何判斷客戶的賬單日、還款日,完成通知的發送?
2、銀行業務系統,夜間要完成跑批的一系列流程,清理數據,下載文件,解析文件,對賬清算、切換結算日期等等。如何觸發一系列流程的執行?
3、金融機構跟人民銀行二代支付系統對接,人民銀行要求低于5W 的金額(小額支付)半個小時打一次包發送,以緩解并發壓力。所以,銀行的跨行轉賬分成了多個流程:錄入、復核、發送。如何把半個小時以內的所有數據一次性發送?
類似于這種1、基于準確的時刻或者固定的時間間隔觸發的任務,或者2、有批量數據需要處理,或者3、要實現兩個動作解耦的場景,我們都可以用任務調度來實現。
任務調度需求分析
任務調度的實現方式有很多,如果要實現我們的調度需求,我們對這個工具有什么樣的基本要求呢?
基本需求
1)可以定義觸發的規則,比如基于時刻、時間間隔、表達式。
2)可以定義需要執行的任務。比如執行一個腳本或者一段代碼。任務和規則是分開的。
3)集中管理配置,持久配置。不用把規則寫在代碼里面,可以看到所有的任務配置,方便維護。重啟之后任務可以再次調度——配置文件或者配置中心。
4)支持任務的串行執行,例如執行A 任務后再執行B 任務再執行C 任務。
5)支持多個任務并發執行,互不干擾(例如ScheduledThreadPoolExecutor)。
6)有自己的調度器,可以啟動、中斷、停止任務。
7)容易集成到Spring。
任務調度工具對比
| 操作系統 | Linux crontab Windows 計劃任務 | 只能執行簡單腳本或者命令 |
| 數據庫 | MySQL、Oracle | 可以操作數據。不能執行Java 代碼 |
| 工具 | Kettle | 可以操作數據,執行腳本。沒有集中配置 |
| 開發語言 | JDK Timer、ScheduledThreadPool | Timer:單線程 JDK1.5 之后:ScheduledThreadPool(Cache、Fiexed、 Single):沒有集中配置,日程管理不夠靈活 |
| 容器 | Spring Task、@Scheduled | 不支持集群 |
| 分布式框架 | XXL-JOB,Elastic-Job | ? |
@Scheduled 也是用JUC 的ScheduledExecutorService 實現的
Scheduled(cron = “0 15 10 15 * ?”)
1、ScheduledAnnotationBeanPostProcessor 的postProcessAfterInitialization 方法將@Scheduled 的方法包裝為指定的task
添加到ScheduledTaskRegistrar 中
2、ScheduledAnnotationBeanPostProcessor 會監聽Spring 的容器初始化事件, 在Spring 容器初始化完成后進行TaskScheduler 實現類實例的查找,若發現有SchedulingConfigurer 的實現類實例,則跳過3
3、查找TaskScheduler 的實現類實例默認是通過類型查找,若有多個實現則會查找名字為"taskScheduler"的實現Bean,若沒有找到則在ScheduledTaskRegistrar 調度任務的時候會創建一個newSingleThreadScheduledExecutor , 將TaskScheduler 的實現類實例設置到ScheduledTaskRegistrar 屬性中
4、ScheduledTaskRegistrar 的scheduleTasks 方法觸發任務調度
5、真正調度任務的類是TaskScheduler 實現類中的ScheduledExecutorService,由J.U.C 提供
Quartz 基本介紹
官網:http://www.quartz-scheduler.org/
Quartz 的意思是石英,像石英表一樣精確。
Quartz is a richly featured, open source job scheduling library that can be integrated within virtually any Java application -
from the smallest stand-alone application to the largest e-commerce system. Quartz can be used to create simple or complex
schedules for executing tens, hundreds, or even tens-of-thousands of jobs; jobs whose tasks are defined as standard Java
components that may execute virtually anything you may program them to do. The Quartz Scheduler includes many
enterprise-class features, such as support for JTA transactions and clustering.
Quatz 是一個特性豐富的,開源的任務調度庫,它幾乎可以嵌入所有的Java 程序,從很小的獨立應用程序到大型商業系統。Quartz 可以用來創建成百上千的簡單的或者復雜的任務,這些任務可以用來執行任何程序可以做的事情。Quartz 擁有很多企業級的特性,包括支持JTA 事務和集群。
Quartz 是一個老牌的任務調度系統,98 年構思,01 年發布到sourceforge。現在更新比較慢,因為已經非常成熟了。
https://github.com/quartz-scheduler/quartz
Quartz 的目的就是讓任務調度更加簡單,開發人員只需要關注業務即可。他是用Java 語言編寫的(也有.NET 的版本)。Java 代碼能做的任何事情,Quartz 都可以調度。
特點:
精確到毫秒級別的調度
可以獨立運行,也可以集成到容器中
支持事務(JobStoreCMT )
支持集群
支持持久化
Quartz Java 編程
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/quick-start.html
引入依賴
<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.0</version> </dependency>默認配置文件
org.quartz.core 包下,有一個默認的配置文件,quartz.properties。當我們沒有定義一個同名的配置文件的時候,就會使用默認配置文件里面的配置。
org.quartz.scheduler.instanceName: DefaultQuartzScheduler org.quartz.scheduler.rmi.export: false org.quartz.scheduler.rmi.proxy: false org.quartz.scheduler.wrapJobExecutionInUserTransaction: false org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount: 10 org.quartz.threadPool.threadPriority: 5 org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true org.quartz.jobStore.misfireThreshold: 60000 org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore創建Job
實現唯一的方法execute(),方法中的代碼就是任務執行的內容。此處僅輸出字符串。
public class MyJob implements Job {public void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("假發在哪里買的");} }在測試類main()方法中,把Job 進一步包裝成JobDetail。
必須要指定JobName 和groupName,兩個合起來是唯一標識符。
可以攜帶KV 的數據(JobDataMap),用于擴展屬性,在運行的時候可以從context獲取到。
JobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").usingJobData("leon","2673").usingJobData("moon",5.21F).build();創建Trigger
在測試類main()方法中,基于SimpleTrigger 定義了一個每2 秒鐘運行一次、不斷重復的Trigger:
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();創建Scheduler
在測試類main()方法中,通過Factory 獲取調度器的實例,把JobDetail 和Trigger綁定,注冊到容器中。
Scheduler 先啟動后啟動無所謂,只要有Trigger 到達觸發條件,就會執行任務。
SchedulerFactory factory = new StdSchedulerFactory(); Scheduler scheduler = factory.getScheduler(); scheduler.scheduleJob(jobDetail, trigger); scheduler.start();注意這里,調度器一定是單例的。
體系結構總結
JobDetail
我們創建一個實現Job 接口的類,使用JobBuilder 包裝成JobDetail,它可以攜帶KV 的數據。
Trigger
定義任務的觸發規律,Trigger,使用TriggerBuilder 來構建。
JobDetail 跟Trigger 是1:N 的關系。
思考:為什么要解耦?
Trigger 接口在Quartz 有4 個繼承的子接口:
| SimpleTrigger | 簡單觸發器 | 固定時刻或時間間隔,毫秒 |
| CalendarIntervalTrigger | 基于日歷的觸發器 | 比簡單觸發器更多時間單位,支持非固定時 間的觸發,例如一年可能365/366,一個月 可能28/29/30/31 |
| DailyTimeIntervalTrigger | 基于日期的觸發器 | 每天的某個時間段 |
| CronTrigger | 基于Cron 表達式的觸發器 | ? |
代碼:standalone com.leon.trigger.TriggerDefine
SimpleTrigger
SimpleTrigger 可以定義固定時刻或者固定時間間隔的調度規則(精確到毫秒)。
例如:每天9 點鐘運行;每隔30 分鐘運行一次。
CalendarIntervalTrigger
CalendarIntervalTrigger 可以定義更多時間單位的調度需求,精確到秒。
好處是不需要去計算時間間隔,比如1 個小時等于多少毫秒。
例如每年、每個月、每周、每天、每小時、每分鐘、每秒。
每年的月數和每個月的天數不是固定的,這種情況也適用。
DailyTimeIntervalTrigger
每天的某個時間段內,以一定的時間間隔執行任務。
例如:每天早上9 點到晚上9 點,每隔半個小時執行一次,并且只在周一到周六執行。
CronTrigger
CronTirgger 可以定義基于Cron 表達式的調度規則,是最常用的觸發器類型。
Cron 表達式
| 1 | 秒 | 0-59 | , - * / |
| 2 | 分鐘 | 0-59 | , - * / |
| 3 | 小時 | 0-23 | , - * / |
| 4 | 日期 | 1-31 | , - * ? / L W C |
| 5 | 月份 | 1-12 | , - * / |
| 6 | 星期 | 1-7 | , - * ? / L W C |
| 7 | 年份(可選) | 1-31 | , - * / |
星號(*):可用在所有字段中,表示對應時間域的每一個時刻,例如,在分鐘字段時,表示“每分鐘”;
問號(?):該字符只在日期和星期字段中使用,它通常指定為“無意義的值”,相當于點位符;
減號(-):表達一個范圍,如在小時字段中使用“10-12”,則表示從10 到12 點,即10,11,12;
逗號(,):表達一個列表值,如在星期字段中使用“MON,WED,FRI”,則表示星期一,星期三和星期五;
斜杠(/):x/y 表達一個等步長序列,x 為起始值,y 為增量步長值。如在分鐘字段中使用0/15,則表示為0,15,30 和45 秒,而5/15 在分鐘字段中表示5,20,35,50,你也可以使用*/y,它等同于0/y;
L:該字符只在日期和星期字段中使用,代表“Last”的意思,但它在兩個字段中意思不同。L 在日期字段中,表示這個月份的最后一天,如一月的31 號,非閏年二月的28 號;如果L 用在星期中,則表示星期六,等同于7。但是,如果L 出現在星期字段里,而且在前面有一個數值X,則表示“這個月的最后X 天”,例如,6L 表示該月的最后星期五;
W:該字符只能出現在日期字段里,是對前導日期的修飾,表示離該日期最近的工作日。例如15W 表示離該月15號最近的工作日,如果該月15 號是星期六,則匹配14 號星期五;如果15 日是星期日,則匹配16 號星期一;如果15號是星期二,那結果就是15 號星期二。但必須注意關聯的匹配日期不能夠跨月,如你指定1W,如果1 號是星期六,結果匹配的是3 號星期一,而非上個月最后的那天。W 字符串只能指定單一日期,而不能指定日期范圍;
LW 組合:在日期字段可以組合使用LW,它的意思是當月的最后一個工作日;
井號(#):該字符只能在星期字段中使用,表示當月某個工作日。如6#3 表示當月的第三個星期五(6 表示星期五,#3 表示當前的第三個),而4#5 表示當月的第五個星期三,假設當月沒有第五個星期三,忽略不觸發;
C:該字符只在日期和星期字段中使用,代表“Calendar”的意思。它的意思是計劃所關聯的日期,如果日期沒有被關聯,則相當于日歷中所有日期。例如5C 在日期字段中就相當于日歷5 日以后的第一天。1C 在星期字段中相當于星期日后的第一天。
Cron 表達式對特殊字符的大小寫不敏感,對代表星期的縮寫英文大小寫也不敏感。
上面我們定義的都是在什么時間執行,但是我們有一些在什么時間不執行的需求,比如:理財周末和法定假日購買不計息;證券公司周末和法定假日休市。
是不是要把日期寫在數據庫中,然后讀取基于當前時間判斷呢?
基于Calendar 的排除規則
如果要在觸發器的基礎上,排除一些時間區間不執行任務,就要用到Quartz 的Calendar 類(注意不是JDK 的Calendar)。可以按年、月、周、日、特定日期、Cron表達式排除。
調用Trigger 的modifiedByCalendar() 添加到觸發器中, 并且調用調度器的addCalendar()方法注冊排除規則。
代碼示例:standalone 工程:com.leon.calendar.CalendarDemo
package com.leon.calendar;import com.leon.job.MyJob1; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.calendar.AnnualCalendar; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar;/*** @Author: qingshan* @Date: 2019/9/5 10:40* @Description: 咕泡學院,只為更好的你*/ public class CalendarDemo {public static void main(String[] args) throws Exception {SchedulerFactory sf = new StdSchedulerFactory();Scheduler scheduler = sf.getScheduler();scheduler.start();// 定義日歷AnnualCalendar holidays = new AnnualCalendar();// 排除咕泡日Calendar leonDay = (Calendar) new GregorianCalendar(2019, 8, 8);holidays.setDayExcluded(leonDay, true);// 排除中秋節Calendar midAutumn = new GregorianCalendar(2019, 9, 13);holidays.setDayExcluded(midAutumn, true);// 排除圣誕節Calendar christmas = new GregorianCalendar(2019, 12, 25);holidays.setDayExcluded(christmas, true);// 調度器添加日歷scheduler.addCalendar("holidays", holidays, false, false);JobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").usingJobData("leon","青山 2673").build();Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().modifiedByCalendar("holidays").withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();Date firstRunTime = scheduler.scheduleJob(jobDetail, trigger);System.out.println(jobDetail.getKey() + " 第一次觸發: " + firstRunTime);} }| BaseCalendar | 為高級的Calendar 實現了基本的功能,實現了org.quartz.Calendar 接口 |
| AnnualCalendar | 排除年中一天或多天 |
| CronCalendar | 日歷的這種實現排除了由給定的CronExpression 表達的時間集合。例如, 您可以使用此日歷使用表達式“* * 0-7,18-23?* *”每天排除所有營業時 間(上午8 點至下午5 點)。如果CronTrigger 具有給定的cron 表達式并 且與具有相同表達式的CronCalendar 相關聯,則日歷將排除觸發器包含的 所有時間,并且它們將彼此抵消。 |
| DailyCalendar | 您可以使用此日歷來排除營業時間(上午8 點- 5 點)每天。每個 DailyCalendar 僅允許指定單個時間范圍,并且該時間范圍可能不會跨越每 日邊界(即,您不能指定從上午8 點至凌晨5 點的時間范圍)。如果屬 性invertTimeRange 為false(默認),則時間范圍定義觸發器不允許觸發 的時間范圍。如果invertTimeRange 為true,則時間范圍被反轉- 也就是 排除在定義的時間范圍之外的所有時間。 |
| HolidayCalendar | 特別的用于從Trigger 中排除節假日 |
| MonthlyCalendar | 排除月份中的指定數天,例如,可用于排除每月的最后一天 |
| WeeklyCalendar | 排除星期中的任意周幾,例如,可用于排除周末,默認周六和周日 |
Scheduler
調度器,是Quartz 的指揮官,由StdSchedulerFactory 產生。它是單例的。并且是Quartz 中最重要的API,默認是實現類是StdScheduler,里面包含了一個QuartzScheduler。QuartzScheduler 里面又包含了一個QuartzSchedulerThread。
Scheduler 中的方法主要分為三大類:
1)操作調度器本身,例如調度器的啟動start()、調度器的關閉shutdown()。
2)操作Trigger,例如pauseTriggers()、resumeTrigger()。
3)操作Job,例如scheduleJob()、unscheduleJob()、rescheduleJob()
這些方法非常重要,可以實現任務的動態調度。
Listener
我們有這么一種需求,在每個任務運行結束之后發送通知給運維管理員。那是不是要在每個任務的最后添加一行代碼呢?這種方式對原來的代碼造成了入侵,不利于維護。如果代碼不是寫在任務代碼的最后一行,怎么知道任務執行完了呢?或者說,怎么監測到任務的生命周期呢?
觀察者模式:定義對象間一種一對多的依賴關系,使得每當一個對象改變狀態,則所有依賴它的對象都會得到通知并自動更新。
Quartz 中提供了三種Listener,監聽Scheduler 的,監聽Trigger 的,監聽Job 的。只需要創建類實現相應的接口,并在Scheduler 上注冊Listener,便可實現對核心對象的監聽。
standalone 工程:com.leon.listener
MyJobListenerTest
MySchedulerListenerTest
MyTriggerListenerTest
package com.leon.listener;import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.JobListener;public class MyJobListener implements JobListener {public String getName() {String name = getClass().getSimpleName();System.out.println( "Method 111111 :"+ "獲取到監聽器名稱:"+name);return name;}public void jobToBeExecuted(JobExecutionContext context) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 222222 :"+ jobName + " ——任務即將執行 ");}public void jobExecutionVetoed(JobExecutionContext context) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 333333 :"+ jobName + " ——任務被否決 ");}public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {String jobName = context.getJobDetail().getKey().getName();System.out.println("Method 444444 :"+ jobName + " ——執行完畢 ");System.out.println("------------------");} } package com.leon.listener;import com.leon.job.MyJob1; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.EverythingMatcher;/*** 測試監聽器*/ public class MyJobListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 創建并注冊一個全局的Job Listenerscheduler.getListenerManager().addJobListener(new MyJobListener(), EverythingMatcher.allJobs());scheduler.start();}} package com.leon.listener;import org.quartz.*;public class MySchedulerListener implements SchedulerListener {public void jobScheduled(Trigger trigger) {String jobName = trigger.getJobKey().getName();System.out.println( jobName + " has been scheduled");}public void jobUnscheduled(TriggerKey triggerKey) {System.out.println(triggerKey + " is being unscheduled");}public void triggerFinalized(Trigger trigger) {System.out.println("Trigger is finished for " + trigger.getJobKey().getName());}public void triggerPaused(TriggerKey triggerKey) {System.out.println(triggerKey + " is being paused");}public void triggersPaused(String triggerGroup) {System.out.println("trigger group "+triggerGroup + " is being paused");}public void triggerResumed(TriggerKey triggerKey) {System.out.println(triggerKey + " is being resumed");}public void triggersResumed(String triggerGroup) {System.out.println("trigger group "+triggerGroup + " is being resumed");}public void jobAdded(JobDetail jobDetail) {System.out.println(jobDetail.getKey()+" is added");}public void jobDeleted(JobKey jobKey) {System.out.println(jobKey+" is deleted");}public void jobPaused(JobKey jobKey) {System.out.println(jobKey+" is paused");}public void jobsPaused(String jobGroup) {System.out.println("job group "+jobGroup+" is paused");}public void jobResumed(JobKey jobKey) {System.out.println(jobKey+" is resumed");}public void jobsResumed(String jobGroup) {System.out.println("job group "+jobGroup+" is resumed");}public void schedulerError(String msg, SchedulerException cause) {System.out.println(msg + cause.getUnderlyingException().getStackTrace());}public void schedulerInStandbyMode() {System.out.println("scheduler is in standby mode");}public void schedulerStarted() {System.out.println("scheduler has been started");}public void schedulerStarting() {System.out.println("scheduler is being started");}public void schedulerShutdown() {System.out.println("scheduler has been shutdown");}public void schedulerShuttingdown() {System.out.println("scheduler is being shutdown");}public void schedulingDataCleared() {System.out.println("scheduler has cleared all data");} } package com.leon.listener;import com.leon.job.MyJob1; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.EverythingMatcher;/*** 測試監聽器*/ public class MySchedulerListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 創建Scheduler Listenerscheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());scheduler.start();}} package com.leon.listener;import org.quartz.JobExecutionContext; import org.quartz.Trigger; import org.quartz.TriggerListener;public class MyTriggerListener implements TriggerListener {private String name;public MyTriggerListener(String name) {this.name = name;}public String getName() {return name;}// Trigger 被觸發,Job 上的 execute() 方法將要被執行時public void triggerFired(Trigger trigger, JobExecutionContext context) {String triggerName = trigger.getKey().getName();System.out.println("Method 11111 " + triggerName + " was fired");}// 在 Trigger 觸發后,Job 將要被執行時由 Scheduler 調用這個方法// 返回true時,這個任務不會被觸發public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {String triggerName = trigger.getKey().getName();System.out.println("Method 222222 " + triggerName + " was not vetoed");return false;}public void triggerMisfired(Trigger trigger) {String triggerName = trigger.getKey().getName();System.out.println("Method 333333 " + triggerName + " misfired");}public void triggerComplete(Trigger trigger, JobExecutionContext context,Trigger.CompletedExecutionInstruction triggerInstructionCode) {String triggerName = trigger.getKey().getName();System.out.println("Method 444444 " + triggerName + " is complete");System.out.println("------------");} } package com.leon.listener;import com.leon.job.MyJob1; import com.leon.listener.MyJobListener; import org.quartz.*; import org.quartz.impl.StdSchedulerFactory; import org.quartz.impl.matchers.EverythingMatcher; import org.quartz.impl.matchers.GroupMatcher; import org.quartz.impl.matchers.KeyMatcher;/*** 測試監聽器*/ public class MyTriggerListenerTest {public static void main(String[] args) throws SchedulerException {// JobDetailJobDetail jobDetail = JobBuilder.newJob(MyJob1.class).withIdentity("job1", "group1").build();// TriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();// SchedulerFactorySchedulerFactory factory = new StdSchedulerFactory();// SchedulerScheduler scheduler = factory.getScheduler();scheduler.scheduleJob(jobDetail, trigger);// 創建并注冊一個全局的Trigger Listenerscheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener1"), EverythingMatcher.allTriggers());// 創建并注冊一個局部的Trigger Listenerscheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener2"), KeyMatcher.keyEquals(TriggerKey.triggerKey("trigger1", "gourp1")));// 創建并注冊一個特定組的Trigger ListenerGroupMatcher<TriggerKey> matcher = GroupMatcher.triggerGroupEquals("gourp1");scheduler.getListenerManager().addTriggerListener(new MyTriggerListener("myListener3"), matcher);scheduler.start();}}JobListener
四個方法:
| getName() | 返回JobListener 的名稱 |
| jobToBeExecuted() | Scheduler 在JobDetail 將要被執行時調用這個方法 |
| jobExecutionVetoed() | jobExecutionVetoed() Scheduler 在JobDetail 即將被執行,但又被TriggerListener 否決了時調用這個 方法 |
| jobWasExecuted() | Scheduler 在JobDetail 被執行之后調用這個方法 |
工具類:ListenerManager,用于添加、獲取、移除監聽器
工具類:Matcher,主要是基于groupName 和keyName 進行匹配。
TriggerListener
| getName() | 返回監聽器的名稱 |
| triggerFired() | Trigger 被觸發,Job 上的execute() 方法將要被執行時,Scheduler 就調用這個 方法 |
| vetoJobExecution() | 在Trigger 觸發后, Job 將要被執行時由Scheduler 調用這個方法。 TriggerListener 給了一個選擇去否決Job 的執行。假如這個方法返回true,這 個Job 將不會為此次Trigger 觸發而得到執行 |
| triggerMisfired() | Trigger 錯過觸發時調用 |
| triggerComplete() | Trigger 被觸發并且完成了Job 的執行時,Scheduler 調用這個方法 |
SchedulerListener
方法比較多,省略。
JobStore
問題:最多可以運行多少個任務(磁盤、內存、線程數)
Jobstore 用來存儲任務和觸發器相關的信息,例如所有任務的名稱、數量、狀態等等。Quartz 中有兩種存儲任務的方式,一種在在內存,一種是在數據庫。
RAMJobStore
Quartz 默認的JobStore 是RAMJobstore,也就是把任務和觸發器信息運行的信息存儲在內存中,用到了HashMap、TreeSet、HashSet 等等數據結構。
如果程序崩潰或重啟,所有存儲在內存中的數據都會丟失。所以我們需要把這些數據持久化到磁盤。
JDBCJobStore
JDBCJobStore 可以通過JDBC 接口,將任務運行數據保存在數據庫中。
JDBC 的實現方式有兩種,JobStoreSupport 類的兩個子類:
JobStoreTX:在獨立的程序中使用,自己管理事務,不參與外部事務。
JobStoreCMT:(Container Managed Transactions (CMT),如果需要容器管理事務時,使用它。
使用JDBCJobSotre 時,需要配置數據庫信息:
org.quartz.jobStore.class:org.quartz.impl.jdbcjobstore.JobStoreTX org.quartz.jobStore.driverDelegateClass:org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 使用quartz.properties,不使用默認配置 org.quartz.jobStore.useProperties:true #數據庫中quartz 表的表名前綴 org.quartz.jobStore.tablePrefix:QRTZ_ org.quartz.jobStore.dataSource:myDS #配置數據源 org.quartz.dataSource.myDS.driver:com.mysql.jdbc.Driver org.quartz.dataSource.myDS.URL:jdbc:mysql://localhost:3306/leon?useUnicode=true&characterEncoding=utf8 org.quartz.dataSource.myDS.user:root org.quartz.dataSource.myDS.password:123456 org.quartz.dataSource.myDS.validationQuery=select 0 from dual問題來了?需要建什么表?表里面有什么字段?字段類型和長度是什么?
在官網的Downloads 鏈接中,提供了11 張表的建表語句:
quartz-2.2.3-distribution\quartz-2.2.3\docs\dbTables
2.3 的版本在這個路徑下:src\org\quartz\impl\jdbcjobstore
表名與作用:
| QRTZ_BLOB_TRIGGERS | Trigger 作為Blob 類型存儲 |
| QRTZ_CALENDARS | 存儲Quartz 的Calendar 信息 |
| QRTZ_CRON_TRIGGERS | 存儲CronTrigger,包括Cron 表達式和時區信息 |
| QRTZ_FIRED_TRIGGERS | 存儲與已觸發的Trigger 相關的狀態信息,以及相關Job 的執行信息 |
| QRTZ_JOB_DETAILS | 存儲每一個已配置的Job 的詳細信息 |
| QRTZ_LOCKS | 存儲程序的悲觀鎖的信息 |
| QRTZ_PAUSED_TRIGGER_GRPS | 存儲已暫停的Trigger 組的信息 |
| QRTZ_SCHEDULER_STATE | 存儲少量的有關Scheduler 的狀態信息,和別的Scheduler 實例 |
| QRTZ_SIMPLE_TRIGGERS | 存儲SimpleTrigger 的信息,包括重復次數、間隔、以及已觸的次數 |
| QRTZ_SIMPROP_TRIGGERS | 存儲CalendarIntervalTrigger 和DailyTimeIntervalTrigger 兩種類型的觸發器 |
| QRTZ_TRIGGERS | 存儲已配置的Trigger 的信息 |
?
總結
以上是生活随笔為你收集整理的任务调度之Quartz1的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自定义线程池-线程池类和测试类编写
- 下一篇: 任务调度之Quartz2