任務排程框架Quartz快速入門!

天喬巴夏丶發表於2020-12-26

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

相關文章