Quartz-scheduler 定時器概述、核心 API 與 快速入門

目錄

quartz-scheduler 石英調度器概述

quartz-scheduler HelloWorld

quartz-scheduler 核心 API

quartz.properties Quartz Configuration

JobDataMap 任務數據對象

Job/JobDetail 實例 與 併發

MVC 訪問調度 Quartz 任務


本文源碼:https://github.com/wangmaoxiong/quartzapp

quartz-scheduler 石英調度器概述

1、Quartz 是功能強大的開源作業調度庫,幾乎可以集成到任何 Java 應用程序中,從最小的獨立應用程序到最大的電子商務系統。Quartz 可用於創建簡單或複雜的計劃,以執行數以萬計的工作;可以執行您編寫的所有內容。

2、Quartz Scheduler 包含許多企業級功能,例如對 JTA 事務和集羣的支持。Quartz 是免費使用的,並根據 Apache 2.0 許可獲得許可。

Quartz 官網:http://www.quartz-scheduler.org/
二進制 jar 包下載地址:http://www.quartz-scheduler.org/downloads/
官網文檔:http://www.quartz-scheduler.org/documentation/
github 開源地址:https://github.com/quartz-scheduler/quartz
w3c 教程:https://www.w3cschool.cn/quartz_doc/

3、Spring Boot 官方也對 Quartz 調度器進行了集成,Spring boot 官網文檔:Quartz Scheduler

4、Java JDK 有原生的 計時器 Timer  以及 定時執行服務 ScheduledExecutorService ,Spring 也提供了 @Scheduled 執行定時任務。

5、生產中如果定時任務多,處理頻繁,則強烈建議使用第三方封裝的調度框架,因爲定時器操作底層都是多線程的操作,任務的啓動、暫停、恢復、刪除、實質是線程的啓動、暫停、中斷、喚醒等操作。

quartz-scheduler HelloWorld

1、本文環境 Spring Boot 2.1.3 + java jdk 1.8 ,pom.xml 文件中導入 quartz 依賴

    <!--quartz 定時器 https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-quartz -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
        <version>2.2.5.RELEASE</version>
    </dependency>

源碼:https://github.com/wangmaoxiong/quartzapp/blob/master/pom.xml

spring-boot-starter-quartz 組件內部依賴瞭如下的組件:

ategory/License Group / Artifact Version
Job Scheduling/Apache 2.0 org.quartz-scheduler » quartz 2.3.2
Apache 2.0 org.springframework » spring-context-support 5.2.4.RELEASE
Transactions/Apache 2.0 org.springframework » spring-tx 5.2.4.RELEASE
Apache 2.0 org.springframework.boot » spring-boot-starter 2.2.5.RELEASE

2、由淺入深,下面先以一個簡單 main 方法調用來介紹 quartz 作業調度器的基本使用步驟。

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 自定義作業實現 org.quartz.Job 接口,execute 方法中寫作業邏輯.
 */
public class HelloJob implements Job {
    private static Logger logger = LoggerFactory.getLogger(HelloJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //獲取觸發器的名稱及其組名稱,獲取作業詳細信息的名稱及其組名稱.
        String triggerName = context.getTrigger().getKey().getName();
        String triggerGroup = context.getTrigger().getKey().getGroup();
        String jobDetailName = context.getJobDetail().getKey().getName();
        String jobDetailGroup = context.getJobDetail().getKey().getGroup();
        logger.info("執行作業,作業名稱={},作業所屬組={},觸發器名稱={},觸發器所屬組={}",
                jobDetailName, jobDetailGroup, triggerName, triggerGroup);
    }
}
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
public class HelloTest {
    public static void main(String[] args) throws SchedulerException, InterruptedException {
        //1)讀取 classpath 下的 quartz.properties(不存在就都使用默認值)配置來實例化 Scheduler
        //可以在類路徑下使用同名文件覆蓋 quartz-x.x.x.jar 包下的 org\quartz\quartz.properties 屬性文件
        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        //2、定義作業的詳細信息,並設置要執行的作業的類名。設置作業的名稱及其組名.
        JobDetail jobDetail = JobBuilder.newJob(HelloJob.class).withIdentity("helloJob", "jobGroup").build();
        //3、創建觸發器,設置觸發器名稱與組名稱,設置 CronTrigger 觸發器的調度規則爲每 10 秒觸發一次.
        //startNow():表示立即觸發任務,否則需要等待下一個觸發點
        CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity("helloTrigger", "triggerGroup")
                .startNow()
                .withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?")).build();
        //4、將 jobDetail 與 trigger 註冊到調度器 scheduler 並啓動。
        scheduler.scheduleJob(jobDetail, cronTrigger);
        scheduler.start();

        TimeUnit.MINUTES.sleep(1);//1分鐘以後停掉調度器
        scheduler.shutdown();
    }
}

quartz-scheduler 核心 API

Scheduler · 調度器

1、Scheduler 用來對 Trigger 和 Job 進行管理,Trigger 和 JobDetail 可以註冊到 Scheduler 中,兩者在 Scheduler 中都擁有自己的唯一的組(group)和名稱(name)用來進行彼此的區分,Scheduler 可以通過組和名稱來對 Trigger 和 JobDetail 進行管理。
2、每個 Scheduler 都有一個 SchedulerContext,用來保存 Scheduler 的上下文數據,Job 和 Trigger 都可以獲取其中的信息。
3、Scheduler 是由 SchedulerFactory 創建,它有兩個實現:DirectSchedulerFactory 、StdSchdulerFactory ,前者可以用來在代碼裏定製 Schduler 參數,後者直接讀取 classpath 下的 quartz.properties(不存在就都使用默認值)配置來實例化 Scheduler。

Job · 任務

1、Job 是一個任務接口,開發者可以實現該接口定義自己的任務,JobExecutionContext 中提供了調度上下文的各種信息。
2、Job 中的任務有可能併發執行,例如任務的執行時間過長,而每次觸發的時間間隔太短,則會導致任務會被併發執行。如果是併發執行,就需要一個數據庫鎖去避免一個數據被多次處理。可以在 execute()方法上添加 @DisallowConcurrentExecution 註解解決這個問題。

JobDetail · 任務詳情

1、JobDetail 對象是在將 job 註冊到 scheduler 時,由客戶端程序創建的,它包含 job 的各種屬性設置,以及用於存儲 job 實例狀態信息的 JobDataMap。
2、JobDetail 由 JobBuilder 創建/定義,Quartz 不存儲 Job 的實際實例,但是允許通過使用 JobDetail 定義一個實例。
3、Job 有一個與其關聯的名稱和組,應該在單個 Scheduler 中唯一標識它們。
4、一個 Trigger(觸發器) 只能對應一個 Job(任務),但是一個 Job 可以對應多個 Trigger。JobDetal 與 Trigger 一對多

Trigger·觸發器

1、Trigger 用於觸發 Job 的執行。TriggerBuilder 用於定義/構建觸發器實例。
2、Trigger也有一個相關聯的 JobDataMap,用於給Job傳遞一些觸發相關的參數。
3、Quartz自帶了各種不同類型的 Trigger,最常用的主要是SimpleTrigger和CronTrigger。

JobDataMap 1、JobDataMap 實現了 JDK 的 Map 接口,可以以 Key-Value 的形式存儲數據。
2、JobDetail、Trigger 實現類中都定義 JobDataMap 成員變量及其 getter、setter 方法,可以用來設置參數信息,Job 執行 execute() 方法的時候,JobExecutionContext 可以獲取到 JobDataMap 中的信息。

1、Scheduler 實例化後,可以啓動(start)、暫停(stand-by)、停止(shutdown)。

2、Scheduler 被停止後,除非重新實例化,否則不能重新啓動;只有當 scheduler 啓動後,trigger纔會被觸發(job纔會被執行;暫停狀態 trigger 不會觸發執行任務。

3、Scheduler 生命週期:從 SchedulerFactory 創建它開始,到 Scheduler 調用 shutdown() 方法結束;Scheduler 被創建後,可以增加、刪除和列舉 Job 和 Trigger,以及執行其它與調度相關的操作(如暫停Trigger)。

4、SimpleTrigger 觸發器主要用於一次性執行的 Job(只在某個特定的時間點執行一次),或者每間隔T個時間單位執行一次。CronTrigger 觸發器基於日曆進行調度,如“每個星期五的正午”,或者“每月的第十天的上午10:15”等。

5、Job 被創建後,可以保存在 Scheduler 中,與 Trigger 是獨立的,同一個 Job 可以有多個 Trigger;這種松耦合的一個好處是可以修改或者替換 Trigger,而不用重新定義與之關聯的 Job。

6、Job 和 Trigger 註冊到 Scheduler 時,可以爲它們設置 key,配置其身份屬性。Job 和 Trigger 的key(JobKey和TriggerKey)可以用於將 Job 和 Trigger 放到不同的分組(group)裏,然後基於分組進行操作。同一個分組下的 Job 或 Trigger 的名稱必須唯一,即一個 Job 或 Trigger 的 key 由名稱(name)和分組(group)組成。

quartz.properties Quartz Configuration

1、Quartz 使用一個名爲 quartz.properties 的屬性文件進行信息配置,必須位於 classpath 下,是 StdSchedulerFactory 用於創建 Scheduler 的默認屬性文件。

2、默認情況下 StdSchedulerFactory 從類路徑下加載名爲 “quartz.properties” 的屬性文件,如果失敗,則加載 org/quartz 包中的“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
#線程池中的線程個數.10個線程表示最多可以同時執行10個任務/作業
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

org.quartz.jobStore.misfireThreshold: 60000
#表示 quartz 中的所有數據,比如作業和觸發器的信息都保存在內存中(而不是數據庫中)
org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

3、quartz.properties 可用屬性的完整配置信息可以參考官網 Quartz Configuration,根據不同的需求,配置分類如下:

  1. Main Configuration (configuration of primary scheduler settings, transactions)
  2. Configuration of ThreadPool (tune resources for job execution)
  3. Configuration of Listeners (your application can receive notification of scheduled events)
  4. Configuration of Plug-Ins (add functionality to your scheduler)
  5. Configuration of RMI Server and Client (use a Quartz instance from a remote process)
  6. Configuration of RAMJobStore (store jobs and triggers in memory)
  7. Configuration of JDBC-JobStoreTX (store jobs and triggers in a database via JDBC)
  8. Configuration of JDBC-JobStoreCMT (JDBC with JTA container-managed transactions)
  9. Configuration of DataSources (for use by the JDBC-JobStores)
  10. Configuration of Database Clustering (achieve fail-over and load-balancing with JDBC-JobStore)
  11. Configuration of TerracottaJobStore (Clustering without a database!)

JobDataMap 任務數據對象

1、創建 JobDetail 時,將要執行的 job 的類名傳給了 JobDetail,所以 scheduler 就知道了要執行何種類型的 job;每次當scheduler 執行 job 的 execute(…) 方法之前會創建該類的一個新的實例執行完畢,對該 Job 實例的引用就被丟棄了,實例會被垃圾回收

2、Job 實現類中必須有一個無參的構造函數(當使用默認的JobFactory時),Job 實現類中,不應該定義有狀態的數據屬性,因爲在 job 的多次執行中,這些屬性的值不會保留。應該使用 JobDataMap 給 Job 實例設置屬性,用於在多次執行中跟蹤 Job 的狀態。

3、JobDetail、Trigger 實現類中都定義 JobDataMap 成員變量及其 getter、setter 方法,可以用來設置參數信息,Job 執行 execute() 方法的時候,JobExecutionContext 可以獲取到 JobDataMap 中的信息。

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
/**
 * @author wangmaoxiong
 */
public class HiJobTest {
    public static void main(String[] args) {
        try {
            //1)讀取 classpath 下的 quartz.properties(不存在就都使用默認值)配置來實例化 Scheduler
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            //2)創建任務詳情。設置任務名稱與所屬組名,同組內的任務名稱必須唯一。
            // 爲任務添加數據,可以直接 usingJobData,也可以先 jobDetail.getJobDataMap(),然後 put
            JobDetail jobDetail = JobBuilder.newJob(HiJob.class)
                    .withIdentity("hiJob", "hiJobGroup")
                    .usingJobData("url", "https://wangmaoxiong.blog.csdn.net/article/details/105057405")
                    .build();

            //3)設置觸發器,設置觸發器名稱與所屬組名,同組內的觸發器名稱必須唯一。
            // 爲觸發器添加數據,可以直接 usingJobData,也可以先 jobDetail.getJobDataMap(),然後 put
            // startNow() 表示啓動後立即執行
            // withSchedule(ScheduleBuilder<SBT> scheduleBuilder):設置觸發器調度計劃,withIntervalInSeconds:間隔多少秒執行
            // repeatForever:表示用於重複。
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("hiTrigger", "hiTriGroup")
                    .startNow()
                    .usingJobData("totalCount", 3)
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
                    .build();

            //4)註冊任務詳情與觸發器,然後啓動
            scheduler.scheduleJob(jobDetail, trigger);
            scheduler.start();
        } catch (SchedulerException e) {
            e.printStackTrace();
        }
    }
}
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
/**
 * @author wangmaoxiong
 * quartz 任務/作業
 */
public class HiJob implements Job {
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        //getJobDetail() 返回 JobDetail, getTrigger() 返回 Trigger,然後都可以獲取自己的 JobDataMap 進行取值或者設值
        //context.getMergedJobDataMap() 返回的 JobDataMap 包含了 JobDetail 與 getTrigger 設置的所有屬性
        JobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap();
        JobDataMap triggerDataMap = context.getTrigger().getJobDataMap();

        String url = jobDetailDataMap.getString("url");
        int totalCount = triggerDataMap.getInt("totalCount");
    }
}

1、context.getMergedJobDataMap() 返回的 JobDataMap 是 JobDetail 中的 JobDataMap 和 Trigger 中的 JobDataMap 的並集,但是如果存在相同的數據,則後者會覆蓋前者的值。
2、如果在 Job 類中爲 JobDataMap 中存儲數據的 key 增加 setter 方法,那麼 Quartz 的默認 JobFactory 實現會在 job 被實例化的時候自動調用這些 setter 方法進行注值,這樣就不需要在 execute() 方法中顯式地從 JobDataMap 中取數據了。

示例源碼:https://github.com/wangmaoxiong/quartzapp/tree/master/src/test/java/com/wmx/quartzapp/helloworld

Job/JobDetail 實例 與 併發

1、一個 Job 實現類可以與多個 JobDetail 實例關聯(JobDetal 與 Trigger 一對多),每一個 JobDetail 實例都有自己的屬性集和 JobDataMap,最後將所有的實例都註冊到 scheduler 中。如 "YearEndSettlementJob" 類實現 Job 接口,該 job 實現需要 JobdataMap 傳入一個參數,表示年終結算的季度(1-4)。因此可以創建該 job 的多個實例(JobDetail),如 "jobDetail1"、"jobDetail2"、"jobDetail3"、"jobDetail4", 然後將季度"1,2,3,4"作爲 JobDataMap 的屬性傳入。當一個 trigger 被觸發時,與之關聯的 JobDetail 實例會被加載,JobDetail 引用的 job 類通過配置在 Scheduler 上的 JobFactory 進行初始化,然後嘗試調用 JobDataMap 中的 key 的 setter 方法注入屬性值。、

2、Job 的狀態數據(JobDataMap)併發性會被下面兩個註解所影響:

@DisallowConcurrentExecution

添加到 Job 的實現類上,表示不要併發地執行 Job 實現的同一個實例(JobDetail)。假如上面的“YearEndSettlementJob”類上有該註解,則同一時刻僅允許執行一個“jobDetail1”實例,但可以併發地執行“jobDetail2”、"jobDetail3"、"jobDetail4" 實例。所以該併發限制實際是針對JobDetail 的,而不是 job 的

@PersistJobDataAfterExecution

添加到 Job 實現類上,表示調度器在成功執行了 job 類的 execute 方法後(沒有發生任何異常),更新 JobDetail 中 JobDataMap 的數據,使得該 JobDetail 在下一次執行的時候JobDataMap 中是更新後的數據,而不是更新前的舊數據。

和 @DisallowConcurrentExecution 註解一樣,儘管註解是加在 job 類上,但其限制作用是針對JobDetil 的,而不是 job 類。

3、生產環境如果任務執行存在併發問題,則強烈建議加上 @PersistJobDataAfterExecution 、@DisallowConcurrentExecution 註解,因爲當同一個 JobDetail 實例被併發執行時,由於競爭,JobDataMap 中存儲的數據很可能是不確定的。

4、當任務的執行時間過長,而觸發的時間間隔小於執行時間,則會導致同一個 JobDetil 實例被併發執行。所以如果想要驗證註解非常簡單,只要將執行間隔縮小,然後 Job 實現類的 execute 方法中使用 Thread.sleep() 硬性延遲。查看任務執行情況,然後加上上面的註解才查看。源碼如下:

https://github.com/wangmaoxiong/quartzapp/blob/master/src/test/java/com/wmx/quartzapp/helloworld/HiJobTest.java

https://github.com/wangmaoxiong/quartzapp/blob/master/src/test/java/com/wmx/quartzapp/helloworld/HiJob.java

5、通過 JobDetail 對象還可以給 job 實例配置的以下屬性:

Durability 指示 job 是否是持久性的。如果 job 是非持久的,當沒有活躍的 trigger 與之關聯時,就會被自動地從 scheduler 中刪除。即非持久的 job 的生命期是由 trigger 的存在與否決定的.
RequestsRecovery 指示  job 遇到故障重啓後,是否是可恢復的。如果 job 是可恢復的,在其執行的時候,如果 scheduler 發生硬關閉(hard shutdown)(比如運行的進程崩潰了,或者關機了),則當 scheduler 重啓時,該 job 會被重新執行。此時該 job 的JobExecutionContext.isRecovering() 返回true。
//2)創建任務詳情。設置任務名稱與所屬組名,同組內的任務名稱必須唯一。
// 爲任務添加數據,可以直接 usingJobData,也可以先 jobDetail.getJobDataMap(),然後 put
JobDetail jobDetail = JobBuilder.newJob(HiJob.class)
	.withIdentity("hiJob", "hiJobGroup")
	.usingJobData("url", "https://wangmaoxiong.blog.csdn.net/article/details/105057405")
	.requestRecovery(true)  //設置  job 遇到故障重啓後,是否是可恢復的,默認爲 false.
	.storeDurably(true)     //設置  job 是否是持久性的,默認爲 false.
	.build();

MVC 訪問調度 Quartz 任務

1、爲了學習 Quartz 方便,所以使用了 mian 運行,其實對於 web 應用操作步驟完全同理,如下源碼所示:

https://github.com/wangmaoxiong/quartzapp/tree/master/src/main/java/com/wmx/quartzapp

2、其中 WeatherController 控制層提供訪問接口,方法中使用 Quartz 調度器調度 WeatherRequestJob 作業,用於定時請求天氣信息接口,獲取城市的天氣數據。

3、WeatherRequestJob 任務中使用 BeanFactoryAware 的方式獲取 Spring 容器中的 RestTemplate 實例,然後請求 http 地址.

4、BeanConfig 類是 @Configuration 配置類,使用 @Bean 將 RestTemplate 實例交由 Spring 容器管理.

5、控制層接口返回的數據使用 ResultData 對象進行封裝,包含 code、message、data 三部分,同時提供 ResultCode 枚舉提供常用的返回值類型。

本文介紹的是內存存儲,即所有的調度信息全部存儲在內存中,如果需要使用 jdbc 存儲到數據庫中,則可以參考《Spring Boot 2.1.3 集成 Quartz 定時器, jdbc 持久化調度信息

 

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