Quartz定時任務調度


什么是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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM