首先所謂的任務調度指的是軟件系統在從某個時間節點開始,以固定的頻率,除去特定的某些時間段,定期執行某項任務,比如可以在某個夜深人靜的時候做一些大批量的文件傳輸、備份等耗費極大資源的工作,那麼通過這個概念可以引出任務調度中的四個核心:
1、時間相關,即如何設定什麼時候開始、如何排除特定時間、如何設定頻率等;
2、執行任務相關,到時間後,程序要幹什麼工作呢?
3、誰來調度任務?即如何將上述1和2關聯起來,然後分配相應的各式資源進行任務調度;
4、如果任務調度過程中,出現程序異常中斷,那麼後續任務如何回覆?
基本上,任務調度的各種框架都圍繞着上述四個核心展開,另外本次本次講解將會用到以下MAVEN依賴:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
示例代碼:
package com.quartz.samples.example1;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/*
* Quartz中,開發者需要實現該接口並實現execute(JobExecutionContext context),在方法中定義需要執行的任務;
* 該方法會傳入JobExecutionContext類型的對象,該對象封裝了調度上下文中各種信息
*/
public class HelloJob implements Job{
public HelloJob() {
// TODO Auto-generated constructor stub
}
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("HelloJob任務得到執行,執行時間爲:" + new Date().toLocaleString());
}
}
package com.quartz.samples.example1;
import java.util.Date;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class SimpleExample {
@SuppressWarnings("deprecation")
public void run() throws SchedulerException {
// 獲取任務調度器
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = stdSchedulerFactory.getScheduler();
// 定義一個JobDetail對象,並將它與HelloJob類進行綁定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "group1").build();
// 構建距離現在下一個偶數分鐘時間
Date startDate = DateBuilder.evenMinuteDateAfterNow();
// 在下一個偶數分鐘出發任務執行
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(startDate).build();
// 告訴quartz,用自定義規則觸發器去調度任務
scheduler.scheduleJob(jobDetail, trigger);
// 啓動任務調度器
scheduler.start();
System.out.println("任務將要在" + startDate.toLocaleString()+ "得到執行!!");
// 然後讓程序睡眠幾秒鐘,以便讓我們的任務調度器有充足的時間去執行任務
try {
Thread.sleep(65L * 1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 停止任務調度器
scheduler.shutdown(true);
}
public static void main(String[] args) throws SchedulerException {
SimpleExample example = new SimpleExample();
example.run();
}
}
輸出結果:
通過上述示例可以發現在Quartz任務調度框架中用:
1、Trigger接口及所有該接口實現類來解決上述核心1
2、JobDetail、Job及實現類來解決上述核心2
3、Scheduler類來解決上述核心3
下面我們來看看Trigger,它是一個類,用來描述Job執行的時間觸發規則。主要有兩個子類:SimpleTrigger和CronTrigger;當僅需要出發一次或者以固定間隔週期性執行時SimpleTrigger是最佳選擇,CronTrigger則通過Cron表達式定義出各種複雜任務方案;
好的,先通過一個簡單例子來實現15秒後開啓一個任務調度,每隔5秒鐘執行一次,總共執行20次,Job實現類內容不變,看代碼:
package com.quartz.samples.example2;
import java.util.Date;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerExample {
@SuppressWarnings("deprecation")
public void run() throws SchedulerException {
// 獲取任務調度器
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = stdSchedulerFactory.getScheduler();
// 定義一個JobDetail對象,並將它與HelloJob類進行綁定
JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("job1", "group1").build();
// 15s後的時間節點
Date startDate = DateBuilder.nextGivenSecondDate(null, 15);
// 在15s後開始,每隔5s執行一次,總共執行20次
SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").withSchedule(SimpleScheduleBuilder.simpleSchedule().withRepeatCount(20).withIntervalInSeconds(5)).startAt(startDate).build();
// 告訴quartz,用自定義規則觸發器去調度任務
scheduler.scheduleJob(jobDetail, trigger);
// 啓動任務調度器
scheduler.start();
System.out.println("任務將要在" + startDate.toLocaleString()+ "得到執行!!");
// 然後讓程序睡眠幾秒鐘,以便讓我們的任務調度器有充足的時間去執行任務
try {
Thread.sleep(350L * 1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 停止任務調度器
scheduler.shutdown(true);
}
public static void main(String[] args) throws SchedulerException {
SimpleTriggerExample example = new SimpleTriggerExample();
example.run();
}
}
部分執行結果如下:
可以看到該類通過with(ScheduleBuilder<SBT> schedBuilder)來指定執行次數和時間間隔,感興趣的可以繼續研究一下源碼,研究完SimpleTrigger類,咱們再來看一下CronTrigger類的用法,可以這麼說在企業級應用裏面用的最多的也是這個,因爲它可以實現複雜任務調度工作,看一個簡單CRON示例:
package com.quartz.samples.example3;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
public class SimpleJob implements Job {
public SimpleJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
JobKey jobKey = context.getJobDetail().getKey();
System.out.println("SimpleJob says: " + jobKey + " executing at " + new Date());
}
}
package com.quartz.samples.example3;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import java.util.Date;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.examples.example3.SimpleJob;
import org.quartz.impl.StdSchedulerFactory;
public class CronTriggerExample {
@SuppressWarnings("deprecation")
public void run() throws SchedulerException {
// 獲取任務調度器
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler sched = stdSchedulerFactory.getScheduler();
// job1將會在每隔20秒執行一次
JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("job1", "group1").build();
CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0/20 * * * * ?")).build();
Date ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: "
+ trigger.getCronExpression());
// job 2 將每隔兩分鐘,執行時間爲15s執行
job = newJob(SimpleJob.class).withIdentity("job2", "group1").build();
trigger = newTrigger().withIdentity("trigger2", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("15 0/2 * * * ?")).build();
ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: "
+ trigger.getCronExpression());
// job 3 將在上午8點到下午5點,每隔兩分鐘執行一次
job = newJob(SimpleJob.class).withIdentity("job3", "group1").build();
trigger = newTrigger().withIdentity("trigger3", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/2 8-17 * * ?")).build();
ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: "
+ trigger.getCronExpression());
// job 4 將在下午5點到11點,每隔3分鐘執行一次
job = newJob(SimpleJob.class).withIdentity("job4", "group1").build();
trigger = newTrigger().withIdentity("trigger4", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0/3 17-23 * * ?")).build();
ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: "
+ trigger.getCronExpression());
// job 5 將每月的1號、15號,每天上午10點執行
job = newJob(SimpleJob.class).withIdentity("job5", "group1").build();
trigger = newTrigger().withIdentity("trigger5", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 10am 1,15 * ?")).build();
ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: "
+ trigger.getCronExpression());
// job 6 將在每週週一到週五,在每分鐘的0s和30秒執行
job = newJob(SimpleJob.class).withIdentity("job6", "group1").build();
trigger = newTrigger().withIdentity("trigger6", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0,30 * * ? * MON-FRI")).build();
ft = sched.scheduleJob(job, trigger);
System.out.println(job.getKey() + " has been scheduled to run at: " + ft + " and repeat based on expression: "
+ trigger.getCronExpression());
// job 7 將只在每週的週六周天,在每分鐘的0s和30秒執行一次
job = newJob(SimpleJob.class).withIdentity("job7", "group1").build();
trigger = newTrigger().withIdentity("trigger7", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0,30 * * ? * SAT,SUN")).build();
ft = sched.scheduleJob(job, trigger);
// 啓動任務調度器
sched.start();
// 等待300s展示結果
try {
Thread.sleep(300L * 1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 停止任務調度器
sched.shutdown(true);
}
public static void main(String[] args) throws SchedulerException {
CronTriggerExample example = new CronTriggerExample();
example.run();
}
}
運行結果就不貼出來了,大家自行復制演示,可以看得出CRON就是用來解決各種複雜時間段的任務類型的;
現在有如下一個需求:想要實現在每週的一、三、五的每天晚上11:30執行某個定時任務,但是要去除某個節假日,比如每年的五一、十一這個該怎麼實現呢?看示例:
package com.quartz.samples.example4;
import static org.quartz.DateBuilder.dateOf;
import static org.quartz.TriggerBuilder.newTrigger;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.examples.example3.SimpleJob;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.calendar.AnnualCalendar;
public class CalendarExample {
@SuppressWarnings("deprecation")
public void run() throws SchedulerException {
// 獲取任務調度器
StdSchedulerFactory stdSchedulerFactory = new StdSchedulerFactory();
Scheduler sched = stdSchedulerFactory.getScheduler();
// 構建所有日期對象集合
AnnualCalendar holidays = new AnnualCalendar();
// 勞動節 5月1日
Calendar firstOfMay = new GregorianCalendar(2016, 4, 1);
holidays.setDayExcluded(firstOfMay, true);
// 國慶節 10月1日
Calendar nationalDay = new GregorianCalendar(2016, 9, 1);
holidays.setDayExcluded(nationalDay, true);
// tell the schedule about our holiday calendar
sched.addCalendar("holidays", holidays, false, false);
// 10月1日上午十點啓動任務調度
Date runDate = dateOf(0, 0, 10, 1, 10);
// job1將會在每週一、三、五的每天晚上11:30得到執行
JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("job1", "group1").build();
CronTrigger trigger = newTrigger().withIdentity("trigger1", "group1")
.withSchedule(CronScheduleBuilder.cronSchedule("0 30 11pm ? * MON,WED,FRI")).modifiedByCalendar("holidays").startAt(runDate).build();
// schedule the job and print the first run date
Date firstRunTime = sched.scheduleJob(job, trigger);
// print out the first execution date.
sched.scheduleJob(job, trigger);
// 啓動任務調度器
sched.start();
// 等待300s展示結果
try {
Thread.sleep(300L * 1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 停止任務調度器
sched.shutdown(true);
}
public static void main(String[] args) throws SchedulerException {
CalendarExample example = new CalendarExample();
example.run();
}
}
通過上述方式,就可以選中排除非工作日了,還算是挺好理解的吧;
最後再看一下任務調度的信息存儲,在默認情況下,Quartz將任務調度的運行信息保存在內存中,這種方式在提供了良好性能的同時,缺乏數據的持久性;比如說,我們執行了一個需要執行100次的任務,運行期間執行到50次,突然系統崩潰了,如果這個時候重新啓動的話,計數器就會從0開始。雖然在實際應用中很少需要一個指定次數的執行任務,但是如果確實需要持久化任務調度信息,Quartz允許用戶通過調整屬性文件,將這些任務調度信息保存到數據庫中,使用數據庫保存任務調度信息後,即便系統崩潰後重新啓動,任務調度的信息將得以恢復,閉上上述例子將從51次開始,首先來認識一下org.quartz包下面的quartz.properties文件,文件內容如下:
# Default Properties file for use by StdSchedulerFactory
# to create a Quartz Scheduler Instance, if a different
# properties file is not explicitly specified.
#
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
在該配置文件的最後一行,發現原來quartz將任務調度信息保存在RAM中是在這裏規定的,那麼也就是說如果我們想將任務調度信息保存到數據庫中需要修改org.quartz.jobStore.class屬性值以及添加部分屬性,首先創建任務調度信息存儲必要的11張表,這些數據庫表的創建信息由quartz提供,創建完畢後會有如下表:
接下來類路徑下面新建一個quartz.properties,一旦創建該文件後,會將org.quartz包下面的quartz.properties覆蓋,在創建該文件時,要提供完整的配置信息,否則在運行時會拋出各種異常,看配置完成後的文件內容:
#============================================================================
# Configure Main Scheduler Properties
#============================================================================
org.quartz.scheduler.instanceName: TestScheduler
org.quartz.scheduler.instanceId: AUTO
org.quartz.scheduler.skipUpdateCheck: true
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount: 3
org.quartz.threadPool.threadPriority: 5
#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.class: org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
org.quartz.jobStore.useProperties: false
org.quartz.jobStore.dataSource: myDS
org.quartz.jobStore.tablePrefix: QRTZ_
org.quartz.jobStore.isClustered: false
#============================================================================
# Configure Datasources
#============================================================================
org.quartz.dataSource.myDS.driver: com.mysql.jdbc.Driver
org.quartz.dataSource.myDS.URL: jdbc:mysql://localhost:3306/quartz
org.quartz.dataSource.myDS.user: root
org.quartz.dataSource.myDS.password: root
org.quartz.dataSource.myDS.maxConnections: 5
然後運行下面這個例子,等任務調度兩次後,強制終止JVM執行,來模擬程序運行過程中中斷、崩潰情況:
package com.quartz.sample.sample2;
import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobKey;
public class SimpleJob implements Job {
public SimpleJob() {
}
public void execute(JobExecutionContext context)
throws JobExecutionException {
// This job simply prints out its job name and the
// date and time that it is running
JobKey jobKey = context.getJobDetail().getKey();
System.out.println("SimpleJob says: " + jobKey + " executing at " + new Date());
}
}
package com.quartz.sample.sample2;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
import java.util.Date;
import org.quartz.DateBuilder;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;
public class SimpleTriggerExample {
public void run() throws Exception {
// First we must get a reference to a scheduler
SchedulerFactory sf = new StdSchedulerFactory();
Scheduler sched = sf.getScheduler();
// get a "nice round" time a few seconds in the future...
Date startTime = DateBuilder.nextGivenSecondDate(null, 15);
// job1 will run 11 times (run once and repeat 10 more times)
// job1 will repeat every 10 seconds
JobDetail job = newJob(SimpleJob.class).withIdentity("job1", "group1").build();
SimpleTrigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(startTime)
.withSchedule(simpleSchedule().withIntervalInSeconds(10).withRepeatCount(10)).build();
sched.scheduleJob(job, trigger);
sched.start();
try {
// wait 33 seconds to show jobs
Thread.sleep(30L * 1000L);
// executing...
} catch (Exception e) {
//
}
sched.shutdown(true);
}
public static void main(String[] args) throws Exception {
SimpleTriggerExample example = new SimpleTriggerExample();
example.run();
}
}
運行上述程序終止後,會發現在數據庫表裏會有好多條運行信息,我剛剛是在程序調度任務執行兩次後終止的程序,所有還有剩下8次需要執行,來看看如何回覆程序運行,上一個簡單示例代碼:
package com.quartz.sample.sample2;
import java.util.List;
import java.util.Set;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.GroupMatcher;
public class JDBCJobStoreRunner {
public static void main(String[] args) {
try {
StdSchedulerFactory factory = new StdSchedulerFactory();
Scheduler schd = factory.getScheduler();
List<String> groupNames = schd.getTriggerGroupNames();
for(int i = 0; i < groupNames.size(); i++){
Set<TriggerKey> keys = schd.getTriggerKeys(GroupMatcher.triggerGroupEquals(groupNames.get(i)));
for(TriggerKey key : keys){
schd.rescheduleJob(key, schd.getTrigger(key));
}
}
schd.start();
} catch (SchedulerException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
運行一下會發現,執行結果爲剩餘的8次任務;
好了,入門知識差不多就這麼多了,後續還會重點針對CRON表達式繼續推出新的博客!!