任務調度框架Quartz快速入門!

Quartz是什麼

Quartz是一個功能強大的開源任務調度庫,幾乎可以集成到任何Java應用程序中,無論是超小型的獨立應用還是超大型電子商務系統。

它常用於企業級應用中:

  • Driving Process Workflow:當新訂單下達,可以安排一個30分鐘內觸發的任務,檢查訂單狀態。
  • System Maintenance:安排每個工作日晚上11點將數據庫內容轉儲到文件的任務。
  • Providing reminder services:提供提醒服務。

Quartz還支持集羣模式和對JTA事務。

Quartz中的重要API及概念

http://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/

超重要API

  • Scheduler 和調度程序交互的主要API
    • 生命週期從SchedulerFactoru創建它開始,到調用shutdown方法結束。
    • 一旦Scheduler創建,任何關於scheduling相關的事,他都爲所欲爲:添加、刪除、列出所有的Jobs和triggers、暫停觸發器等。
    • 在start方法之前,不會做任何事情。
  • Job 你希望被調度器調度的任務組件接口。
    • 當Job的觸發器觸發時,調度程序的工作線程將調用execute方法。
    • 該方法接收一個JobExecutionContext 對象,爲Job實例提供了豐富的運行時環境信息,比如:scheduler、trigger、jobDataMap、job、calendar、各種time等。
  • JobDetail 用於定義任務。
    • JobDetail對象由Quartz客戶端在將job加入Scheduler提供,也就是你的程序。
    • 它包含了不同爲job設置的屬性,還有可以用來爲job儲存狀態信息的JobDataMap。
    • 注意它和Job的區別,它實際上是Job實例的屬性。【Job定義如何執行,JobDetail定義有何屬性】
  • Trigger 觸發任務執行。
    • 觸發器可能具有與之關聯的JobDataMap,以便於將特定於觸發器觸發的參數傳遞給Job。
    • Quartz提供了幾種不同的觸發器,SimpleTrigger和CronTrigger比較常用。
    • 如果你需要一次性執行作業或需要在給定的時間觸發一個作業並重復執行N次且有兩次執行間有延時delay,SimpleTrigger較爲方便。
    • 如果你希望基於類似日期表觸發執行任務,CronTrgger推薦使用。
  • JobBuilder 用於構建JobDetail的。
  • TriggerBuilder 用於構建Trigger的。

Quartz提供了各種各樣的Builder類,定義了Domain Specific Language,且都提供了靜態的創建方法,我們可以使用import static簡化書寫。

重要概念

  • Identity
    • 當作業和觸發器在Quartz調度程序中註冊時,會獲得標識鍵。
    • JobKey和TriggerKey允許被置入group中,易於組織管理。
    • 唯一的,是name和group的組合標識。
  • JobDataMap
    • 是Map的實現,具有key-value相關操作,存儲可序列化數據對象,供Job實例在執行時使用。
    • 可以使用usingJobData(key,value)在構建Jobdetail的時候傳入數據,使用jobDetail.getJobDataMap()獲取map。

Quartz設計理念:爲什麼設計Job和Trigger?

While developing Quartz, we decided that it made sense to create a separation between the schedule and the work to be performed on that schedule. This has (in our opinion) many benefits.

For example, Jobs can be created and stored in the job scheduler independent of a trigger, and many triggers can be associated with the same job. Another benefit of this loose-coupling is the ability to configure jobs that remain in the scheduler after their associated triggers have expired, so that that it can be rescheduled later, without having to re-define it. It also allows you to modify or replace a trigger without having to re-define its associated job.

隔離schedule和schedule上執行的Job,優點是可見的:

可以獨立於觸發器創建作業並將其存儲在作業調度程序中,並且許多觸發器可以與同一作業相關聯。這樣的松耦合好處是什麼?

  • 如果觸發器過期,作業還可以保留在程序中,以便重新調度,而不必重新定義。
  • 如果你希望修改替換某個觸發器,你不必重新定義其關聯的作業。

最簡單的Quartz使用案例

導入依賴

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.2</version>
</dependency>

簡單案例如下

public class QuartzTest {
	// 你需要在start和shutdown之間執行你的任務。
    public static void main(String[] args) {

        try {
            // 從工廠中獲取Scheduler示例
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

            // 開始
            scheduler.start();

            // 定義Job,並將其綁定到HelloJob類中
            JobDetail job = JobBuilder.newJob(HelloJob.class)
                    .withIdentity("job1", "group1") // name 和 group
                    .usingJobData("username", "天喬巴夏") // 置入JobDataMap
                    .usingJobData("age", "20")
                    .withDescription("desc-demo")
                    .build();

            // 觸發Job執行,每40s執行一次
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1")
                    .startNow() // 立即開始
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInSeconds(40)
                            .repeatForever())
                    .build();

            // 告訴 quartz 使用trigger來調度job
            scheduler.scheduleJob(job, trigger);
			// 關閉,線程終止
            scheduler.shutdown();

        } catch (SchedulerException se) {
            se.printStackTrace();
        }
    }

}

@Slf4j
@NoArgsConstructor
public class HelloJob implements Job {

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        // 從context中獲取屬性
        JobDetail jobDetail = context.getJobDetail();
        JobDataMap jobDataMap = jobDetail.getJobDataMap();
        JobKey key = jobDetail.getKey();
        Class<? extends Job> jobClass = jobDetail.getJobClass();
        String description = jobDetail.getDescription();

        String username = jobDataMap.getString("username");
        int age = jobDataMap.getIntValue("age");

        log.info("\nJobKey : {},\n JobClass : {},\n JobDesc : {},\n username : {},\n age : {}",
                key, jobClass.getName(), description, username, age);
    }
}

啓動測試,打印日誌如下:

01:23:12.406 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
01:23:12.414 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
01:23:12.430 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
01:23:12.430 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
01:23:12.432 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
01:23:12.433 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'
  Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.
  NOT STARTED.
  Currently in standby mode.
  Number of jobs executed: 0
  Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.
  Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.

01:23:12.433 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
01:23:12.433 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
01:23:12.434 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
01:23:12.434 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
01:23:12.443 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
01:23:12.445 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.job1', class=com.hyhwky.HelloJob
01:23:12.451 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
01:23:12.452 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.job1
01:23:12.452 [DefaultQuartzScheduler_Worker-1] INFO com.hyhwky.HelloJob - 
JobKey : group1.job1,
 JobClass : com.hyhwky.HelloJob,
 JobDesc : desc-demo,
 username : 天喬巴夏,
 age : 20

我們可以看到quartz已經被初始化了,初始化配置如下,在org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar!\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 
org.quartz.threadPool.threadCount: 10
org.quartz.threadPool.threadPriority: 5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true

# 存儲配置
org.quartz.jobStore.misfireThreshold: 60000 #trigger 容忍時間60s

org.quartz.jobStore.class: org.quartz.simpl.RAMJobStore

更多的配置:Quartz Configuration Reference

Job實例和JobDetail

Job和JobDetail

  • Job是正在執行的作業實例,JobDetail是作業定義。
  • 一個Job可以創建多個JobDetail,擁有不同的JobDataMap。

這樣定義有什麼好處呢?舉個例子吧,假設現在你定義了一個類實現了Job接口,比如:SendEmailJob。如果你希望根據用戶的姓名,選擇指定的人發送,那麼你可以通過JobDataMap綁定參數傳遞進JobDetail中,也就是說我們需要創建兩個不同的JobDetail,比如:SendEmailToSummerday和SendEmailToTQBX,他們擁有各自獨立的JobDataMap,實現更加靈活。

Job的State和Concurrency

https://blog.csdn.net/fly_captain/article/details/83029440

@DisallowConcurrentExecution

該註解標註在Job類上,意思是不能併發執同一個JobDetail定義的多個實例,但可以同時執行多個不同的JobDetail定義的實例。

拿上面的例子繼續舉例,假設SendEmailJob標註了此註解,表明同一時間可以同時執行SendEmailToSummerday和SendEmailToTQBX,因爲他們是不同的JobDetail,但是不能同時執行多個SendEmailToSummerday。

@PersistJobDataAfterExecution

該註解也標註在Job類上,告訴Scheduler正常執行完Job之後,重新存儲更新一下JobDataMap。一般標註此註解的Job類應當考慮也加上@DisallowConcurrentExecution註解,以避免同時執行Job時出現JobDataMap存儲的競爭。

Trigger常見使用

更多例子可以查看文檔,非常詳細:SimpleTriggerCronTrigger

構建一個觸發器,該觸發器將立即觸發,然後每五分鐘重複一次,直到小時 22:00:

 
import static org.quartz.TriggerBuilder.*;
import static org.quartz.SimpleScheduleBuilder.*;
import static org.quartz.DateBuilder.*: 

 SimpleTrigger trigger = (SimpleTrigger) newTrigger()
    .withIdentity("trigger7", "group1")
    .withSchedule(simpleSchedule()
        .withIntervalInMinutes(5)
        .repeatForever())
    .endAt(dateOf(22, 0, 0))
    .build();

構建一個觸發器,該觸發器將在週三上午 10:42 觸發,在系統默認值以外的時區中:

import static org.quartz.TriggerBuilder.*;
import static org.quartz.CronScheduleBuilder.*;
import static org.quartz.DateBuilder.*:

 trigger = newTrigger()
    .withIdentity("trigger3", "group1")
    .withSchedule(cronSchedule("0 42 10 ? * WED")) // [秒] [分] [時] [月的第幾天] [月] [一星期的第幾天] [年(可選)]
    .inTimeZone(TimeZone.getTimeZone("America/Los_Angeles"))
    .forJob(myJobKey)
    .build();

Quartz存儲與持久化

Job的持久化是非常重要的,如果Job不能持久化,一旦不再有與之關聯的Trigger,他就會自動從調度程序中被刪除。

Job存儲器的類型:

類型 優點 缺點
RAMJobStore 不要外部數據庫,配置容易,運行速度快 因爲調度程序信息是存儲在被分配給 JVM 的內存裏面,所以,當應用程序停止運行時,所有調度信息將被丟失。另外因爲存儲到JVM內存裏面,所以可以存儲多少個 Job 和 Trigger 將會受到限制
JDBC 作業存儲 支持集羣,因爲所有的任務信息都會保存到數據庫中,可以控制事物,還有就是如果應用服務器關閉或者重啓,任務信息都不會丟失,並且可以恢復因服務器關閉或者重啓而導致執行失敗的任務 運行速度的快慢取決與連接數據庫的快慢

具體的使用方法:http://www.quartz-scheduler.org/documentation/quartz-2.2.2/tutorials/tutorial-lesson-09.html

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