什么是Quartz
Quartz是OpenSymphony開源組織在Job scheduling領域又一個開源項目,完全由Java開發,可以用來執行定時任務,類似於java.util.Timer。但是相較於Timer, Quartz增加了很多功能。
Quartz就是一種任務調度計划。
- 它是由OpenSymphony提供的、開源的、java編寫的強大任務調度框架
- 幾乎可以集成到任何規模的運用程序中,如簡單的控制台程序,復雜的大規模分布式電子商務系統
- 可用於創建簡單的或復雜的計划任務
- 包含很多企業級功能,如支持JTA和集群等
Quartz應用場景
大部分公司都會用到定時任務這個功能。
拿火車票購票來說,當你下單后,后台就會插入一條待支付的task(job),一般是30分鍾,超過30min后就會執行這個job,去判斷你是否支付,未支付就會取消此次訂單;當你支付完成之后,后台拿到支付回調后就會再插入一條待消費的task(job),Job觸發日期為火車票上的出發日期,超過這個時間就會執行這個job,判斷是否使用等。
在我們實際的項目中,當Job過多的時候,肯定不能人工去操作,這時候就需要一個任務調度框架,幫我們自動去執行這些程序。那么該如何實現這個功能呢?
(1)首先我們需要定義實現一個定時功能的接口,我們可以稱之為Task(或Job),如定時發送郵件的task(Job),重啟機器的task(Job),優惠券到期發送短信提醒的task(Job),實現接口如下:
(2)有了任務之后,還需要一個能夠實現觸發任務去執行的觸發器,觸發器Trigger最基本的功能是指定Job的執行時間,執行間隔,運行次數等。
(3)有了Job和Trigger后,怎么樣將兩者結合起來呢?即怎樣指定Trigger去執行指定的Job呢?這時需要一個Schedule,來負責這個功能的實現。
上面三個部分就是Quartz的基本組成部分:
- 調度器:Scheduler
- 任務:JobDetail
- 觸發器:Trigger,包括SimpleTrigger和CronTrigger
Quartz原理
當要深入研究一個技術時,研究它的體系結構和內部運行原理,不失為一種較好的方式。同理,我們在研究Quartz時,也采用類似的方法,
下圖為Quartz的大致結構圖。
Quartz的模塊
Quartz幾個關鍵概念
Job
負責定義任務所處理的邏輯,實現類需要實現org.quartz.Job接口,是Quartz中的一個接口,接口下只有execute方法,在這個方法中編寫業務邏輯。
public interface Job { void execute(JobExecutionContext context) throws JobExecutionException; }
JobDetail
JobDetail,顧名思義,就是表示關於每個Job的相關信息,它主要包括兩個核心組件,即Job Task和JobData Map。
JobDetail用來綁定Job,為Job實例提供許多屬性:
- name
- group
- jobClass
- jobDataMap
JobDetail綁定指定的Job,每次Scheduler調度執行一個Job的時候,首先會拿到對應的Job,然后創建該Job實例,再去執行Job中的execute()的內容,任務執行結束后,關聯的Job對象實例會被釋放,且會被JVM GC清除。
JobDetail定義的是任務數據,而真正的執行邏輯是在Job中。
為什么設計成JobDetail + Job,不直接使用Job
JobDetail定義的是任務數據,而真正的執行邏輯是在Job中。
這是因為任務是有可能並發執行,如果Scheduler直接使用Job,就會存在對同一個Job實例並發訪問的問題。而JobDetail & Job 方式,Sheduler每次執行,都會根據JobDetail創建一個新的Job實例,這樣就可以規避並發訪問的問題。
Trigger
Trigger,表示觸發器,根據配置規則來觸發執行計划調度job,它主要包括兩個核心組件,即SimpleTrigger和CronTrigger。其他Tigger基本都可以通過這兩種實現。Trigger還可以定義錯過的任務如何處理。
SimpleTrigger
SimpleTrigger可以實現在一個指定時間段內執行一次作業任務或一個時間段內多次執行作業任務。
下面的程序就實現了程序運行5s后開始執行Job,執行Job 5s后結束執行:
Date startDate = new Date(); startDate.setTime(startDate.getTime() + 5000); Date endDate = new Date(); endDate.setTime(startDate.getTime() + 5000); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1") .usingJobData("trigger1", "這是jobDetail1的trigger") .startNow()//立即生效 .startAt(startDate) .endAt(endDate) .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1)//每隔1s執行一次 .repeatForever()).build();//一直執行
CronTrigger
CronTrigger功能非常強大,是基於日歷的作業調度,而SimpleTrigger是精准指定間隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基於Cron表達式的,先了解下Cron表達式:
由7個子表達式組成字符串的。
下面的代碼就實現了每周一到周五上午10:30執行定時任務:
/** * Created by wanggenshen * Date: on 2018/7/7 20:06. * Description: XXX */ public class MyScheduler2 { public static void main(String[] args) throws SchedulerException, InterruptedException { // 1、創建調度器Scheduler SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); // 2、創建JobDetail實例,並與PrintWordsJob類綁定(Job執行內容) JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class) .usingJobData("jobDetail1", "這個Job用來測試的") .withIdentity("job1", "group1").build(); // 3、構建Trigger實例,每隔1s執行一次 Date startDate = new Date(); startDate.setTime(startDate.getTime() + 5000); Date endDate = new Date(); endDate.setTime(startDate.getTime() + 5000); CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1") .usingJobData("trigger1", "這是jobDetail1的trigger") .startNow()//立即生效 .startAt(startDate) .endAt(endDate) .withSchedule(CronScheduleBuilder.cronSchedule("* 30 10 ? * 1/5 2018")) .build(); //4、執行 scheduler.scheduleJob(jobDetail, cronTrigger); System.out.println("--------scheduler start ! ------------"); scheduler.start(); System.out.println("--------scheduler shutdown ! ------------"); } }
JobExecutionContext
JobExecutionContext中包含了Quartz運行時的環境以及Job本身的詳細數據信息。
當Schedule調度執行一個Job的時候,就會將JobExecutionContext傳遞給該Job的execute()中,Job就可以通過JobExecutionContext對象獲取信息。
主要信息有:
JobDataMap
JobDataMap實現了JDK的Map接口,可以以Key-Value的形式存儲數據。
JobDetail、Trigger都可以使用JobDataMap來設置一些參數或信息,
Job執行execute()方法的時候,JobExecutionContext可以獲取到JobExecutionContext中的信息:
JobDetail jobDetail = JobBuilder.newJob(PrintWordsJob.class)
.usingJobData("jobDetail1", "這個Job用來測試的") .withIdentity("job1", "group1").build(); Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "triggerGroup1") .usingJobData("trigger1", "這是jobDetail1的trigger") .startNow()//立即生效 .withSchedule(SimpleScheduleBuilder.simpleSchedule() .withIntervalInSeconds(1)//每隔1s執行一次 .repeatForever()).build();//一直執行
Job執行的時候,可以獲取到這些參數信息:
@Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { System.out.println(jobExecutionContext.getJobDetail().getJobDataMap().get("jobDetail1")); System.out.println(jobExecutionContext.getTrigger().getJobDataMap().get("trigger1")); String printTime = new SimpleDateFormat("yy-MM-dd HH-mm-ss").format(new Date()); System.out.println("PrintWordsJob start at:" + printTime + ", prints: Hello Job-" + new Random().nextInt(100)); }
JobStore
JobStore,表述任務存儲器,主要存儲job和trigger相關信息。
SchedulerFactory
SchedulerFactory負責初始化,讀取配置文件,然后創建Scheduler。
Scheduler
Scheduler,表述任務計划,中樞調度器,負責管理Trigger/JobDetail和3個調度線程,具體job和job相關trigger就能夠被注入其中,從而實現任務計划調度。其主要常用的方法:
- Start --啟動執行計划
- Shutdowm --關閉執行計划
QuartzSchedulerThread
主調度線程
MisfireHandler
錯失觸發的任務恢復線程,。更新Trigger的觸發時間。
ClusterManager
集群協調線程。定期心跳,自動recover。同主程序中的recover。
示例一
引入Maven依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-quartz</artifactId> </dependency>
編寫具體的job
public class TestJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext context) throws JobExecutionException { System.out.println("Test job executed."); } }
定義JobDetail, 把Job的class類型傳入。Spring自動處理
@Bean public JobDetail testJob(){ return JobBuilder .newJob(TestJob.class) .withIdentity("TestJob") .storeDurably() .requestRecovery() .build(); }
定義Trigger, 通過Key關聯JobDetail。Spring 自動處理。
@Bean public Trigger testTrigger(){ return TriggerBuilder.newTrigger() .withIdentity("TestTrigger") .forJob("TestJob") .withSchedule(CronScheduleBuilder .cronSchedule("0/6 * * * * ? ") .withMisfireHandlingInstructionDoNothing()) .build(); }
如果需要不同的數據庫,定義一個@Primary主庫,和一個@QuartzDataSource quartz專用庫
@Bean @Primary @ConfigurationProperties(prefix="spring.datasource") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean @QuartzDataSource @ConfigurationProperties(prefix="spring.datasource.quartz") public DataSource quartzDataSource() { return DataSourceBuilder.create().build(); }
application.properties 都有默認配置,第一行啟用數據庫,后面兩行是cluster功能
spring.quartz.job-store-type=jdbc spring.quartz.org.quartz.scheduler.instanceId = AUTO spring.quartz.org.quartz.jobStore.isClustered = true
雖然Spring提供了自動建庫的功能,但是第一次建完之后需要改成never
spring.quartz.jdbc.initializeSchema=ALWAYS
#spring.quartz.jdbc.initializeSchema=NEVER
參考:
(1)https://www.cnblogs.com/wangjiming/p/10027439.html
(2)https://blog.csdn.net/noaman_wgs/article/details/80984873
(3)https://www.jianshu.com/p/bac0e919f3ee