Java任務調度之Quartz快速入門

首先所謂的任務調度指的是軟件系統在從某個時間節點開始,以固定的頻率,除去特定的某些時間段,定期執行某項任務,比如可以在某個夜深人靜的時候做一些大批量的文件傳輸、備份等耗費極大資源的工作,那麼通過這個概念可以引出任務調度中的四個核心:

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表達式繼續推出新的博客!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章