版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/catoop/article/details/70246683
核心概念
1.Job:Job是任務執行的流程,是一個類
2.JobDetail:JobDetail是Job是執行個體,是一個對象,包含了該執行個體的執行計劃和所需要的資料
3.Trigger:Trigger是定時器,決定任務何時執行
4.Scheduler:排程器,排程器接受一組JobDetail+Trigger即可安排一個任務,其中一個JobDetail可以關聯多個Trigger
執行個體
1.初始化:
-
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
-
scheduler.start();
當程式退出時,應該主動調用shutdown()方法關閉該排程器。
2.Job:
一個Job類需要實作org.quartz.Job接口,這個接口值需要實作一個方法即可:
-
void execute(JobExecutionContext context) throws JobExecutionException
context是重要的上下文,可以通路到關聯的JobDetail對象和本次觸發的Trigger對象,以及在此之上設定的資料。
3.JobDetail:
可以使用JobBuilder來建構一個JobDetail對象:
-
JobDetail job = JobBuilder.newJob(MyJob.class) // MyJob是我實作的Job類
-
。也可以 .withIdentity("myjob", "group1").withIdentity("myjob") // 可以給該JobDetail起一個id,便于之後的檢索
-
.requestRecovery() // 執行中應用發生故障,需要重新執行
-
.storeDurably() // 即使沒有Trigger關聯時,也不需要删除該JobDetail
-
.usingJobData("key1", "value1")
-
.usingJobData("key2", "value2") // 以Key-Value形式關聯資料
-
.build();
Quartz因為考慮到有些任務不是幂等的,不可以多次重複執行,是以預設沒有開啟“requestRecovery”。當确認業務中允許一次任務執行兩次的情況下,可以開啟該選項,則任務肯定不會因為應用停止而漏調用,但缺點就是,有可能會重複調用。
每個JobDetail内都有一個Map,包含了關聯到這個Job的資料,在Job類中,可以通過context取出該資料,進行業務流程處理。
4.Trigger:
可以使用TriggerBuilder來建構一個Trigger對象:
-
Trigger trigger = TriggerBuilder.newTrigger()
-
.forJob("myjob") // 關聯上述的JobDetail
-
.withIdentity("myjob-trigger1") // 給該Trigger起一個id
-
.startAt(DateBuilder.futureDate(20, IntervalUnit.SECOND)) // 延遲20秒開始
-
.withSchedule(SimpleScheduleBuilder.repeatMinutelyForever()) // 每分鐘觸發一次,無限循環
-
.usingJobData("key3", "value3")
-
.usingJobData("key4", "value4") // 以Key-Value形式關聯資料
-
.build();
5.設定:
因為上述的Trigger已經關聯了JobDetail,可以使用
-
scheduler.scheduleJob(trigger);
把這一組JobDetail和Trigger加載到排程器上,接下來就會按照計劃執行Job任務。
6.配置檔案:
配置檔案不是必須的,Quartz對配置項都是有預設值的,當需要自定義的時候,可以在classpath路徑下放一個quartz.properties檔案,Quartz的StdSchedulerFactory在啟動時會自動加載該配置檔案。
比較值得關注的是這兩個配置項:
-
org.quartz.threadPool.threadCount=50
-
org.quartz.scheduler.batchTriggerAcquisitionMaxCount=50
第一個配置項是線程池裡的線程數,預設值是10,當執行任務會并發執行多個耗時任務時,要根據業務特點選擇線程池的大小。
第二個配置是,當檢查某個Trigger應該觸發時,預設每次隻Acquire一個Trigger,(為什麼要有Acquire的過程呢?是為了防止多線程通路的情況下,同一個Trigger被不同的線程多次觸發)。尤其是使用JDBC JobStore時,一次Acquire就是一個update語句,盡可能一次性的多擷取幾個Trigger,一起觸發,當定時器數量非常大的時候,這是個非常有效的優化。當定時器數量比較少時,觸發不是極為頻繁時,這個優化的意義就不大了。
6.持久化
如果定時器在業務中屬于關鍵資料,需要在故障重新開機後恢複狀态,則需要把Quartz配置為持久化模式。預設情況下,所有定時任務和資料都儲存在記憶體中,在應用重新開機後狀态會消失。
JobStore決定了Quartz如何存儲任務和觸發器,預設值是org.quartz.simpl.RAMJobStore,我們需要把它配置為org.quartz.impl.jdbcjobstore.JobStoreTX,即可以使用JDBC資料源,把狀态持久化到關系型資料庫中。
用H2資料庫進行舉例,配置檔案如下:
-
org.quartz.dataSource.DATA_SOURCE_NAME.driver=org.h2.Driver
-
org.quartz.dataSource.DATA_SOURCE_NAME.URL=jdbc:h2:quartz;MVCC=TRUE
-
org.quartz.dataSource.DATA_SOURCE_NAME.user=sa
-
org.quartz.dataSource.DATA_SOURCE_NAME.password=
-
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
-
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
-
org.quartz.jobStore.dataSource=DATA_SOURCE_NAME
-
# org.quartz.jobStore.useDBLocks=true
Quartz為了保證多線程下的一緻性,使用了鎖,但是使用了使用Lock表裡的一行記錄來模拟鎖的形式來實作的,其實性能很糟糕,還不如使用預設的基于Java的Monitor鎖的效果好,是以我注釋掉了這個配置項。
之後,打開quartz-x.x.x.tar.gz包内的docs/dbTables檔案夾内有各種資料庫下的建表語句,直接在資料庫中執行,把表先建好。
配置檔案中增加上述對JobStore的設定後,代碼不用修改一行,所有的任務和觸發器已經是持久化的了,當應用停機重新開機後,錯過的(misfire)任務和上次正在執行的(recovery)任務都會回複狀态,如果JobDetail指定了requestRecovery,上次執行中,但沒有執行完畢的任務會重新執行一遍。
注意事項:
> 一個任務JOB可以添加多個Trigger 但是一個Trigger隻能綁定一個JOB 這點需要注意。
> 在用JobBuilder建立JobDetail的時候,有一個storeDurably()方法,可以在沒有觸發器指向任務的時候,使用 sched.addJob(job, true) 将任務儲存在隊列中了。而後使用 sched.scheduleJob 觸發。如果不使用 storeDurably ,則在添加 Job 到引擎的時候會抛異常,意思就是該 Job 沒有對應的 Trigger。
> 要求cluster上所有的node的時間應該是一樣的。