- 微服務(wù)從小白到專家:Spring Cloud和Kubernetes實(shí)戰(zhàn)
- 姚秋辰 張昕 卿睿
- 2992字
- 2021-10-29 12:24:32
3.7 定時(shí)任務(wù)
在傳統(tǒng)節(jié)假日,我們會收到各種祝福短信,中國的證券交易系統(tǒng)和基金銷售公司每天晚上都要向中國證券登記結(jié)算有限責(zé)任公司提交每日的股票、基金交易匯總數(shù)據(jù),公司的ERP系統(tǒng)每月每季度都要生產(chǎn)各種報(bào)表,這類功能都是通過定時(shí)任務(wù)完成的。定時(shí)任務(wù)的實(shí)現(xiàn)方式有很多種,例如UNIX的Crontab、Java生態(tài)的Quartz,本節(jié)將以Quartz為基礎(chǔ)在Spring體系下實(shí)現(xiàn)定時(shí)任務(wù)。
3.7.1 Quartz
3.7.1.1 Quartz Scheduler簡介
開源領(lǐng)域有很多定時(shí)任務(wù)框架,但是在Java生態(tài)中Quartz一定是最優(yōu)秀的。Quartz提供了各種從簡單到復(fù)雜的定時(shí)任務(wù)框架供開發(fā)者使用,圖3-96展示了Quartz的架構(gòu)及工作模式。

圖3-96 Quartz的架構(gòu)及工作模式
圖3-96中每個(gè)組件的描述如下:
? Quartz Scheduler:維護(hù)Job(任務(wù))和Trigger(觸發(fā)器)之間的關(guān)系,即Job是被哪些Trigger觸發(fā)的。
? Quartz Scheduler Thread:該線程從Job Store獲得Trigger,并在指定的時(shí)間觸發(fā)Trigger。
? Job:該接口對任務(wù)執(zhí)行的流程做了代碼邏輯抽象,可以認(rèn)為Job是一項(xiàng)可被執(zhí)行的任務(wù)。
? Trigger:Trigger是一種制定任務(wù)計(jì)劃的機(jī)制,它用于定義Job應(yīng)該在什么時(shí)候被執(zhí)行。
? Job Store:用于保存Job和Trigger的相關(guān)信息。
? ThreadPool:用于管理執(zhí)行Job任務(wù)的線程。
Quartz有以下七個(gè)核心類:
(1)org.Quartz.Scheduler
Quartz Scheduler的主要接口,功能是將Job和Triggers關(guān)聯(lián)起來,并在Trigger被觸發(fā)時(shí)執(zhí)行Job。
(2)org.Quartz.SchedulerFactory
主要用于創(chuàng)建Scheduler對象實(shí)例,具體代碼如下:

(3)org.Quartz.Job
Job任務(wù)的抽象層接口,所有被Quartz框架執(zhí)行的任務(wù)都必須實(shí)現(xiàn)這個(gè)接口,具體代碼如下:

(4)org.Quartz.JobDetail
Quartz需要通過Job Group和Job Name區(qū)分各個(gè)不同的Job,但它并不會存儲Job實(shí)例,而是以JobDetail的方式來存儲和標(biāo)記Job。示例代碼如下:

(5)org.Quartz.Trigger
Trigger定義了一個(gè)Job在什么時(shí)間點(diǎn)會被執(zhí)行,一個(gè)Job可以有多個(gè)Trigger,但是一個(gè)Trigger只能綁定一個(gè)Job。最常見的兩個(gè)Trigger實(shí)例是SimpleTrigger和CronTrigger,下面的例子就通過TriggerBuilder定義了一個(gè)每天10:42:00運(yùn)行的Trigger,具體代碼如下:

定義Trigger時(shí)還有一個(gè)重要的概念就是Misfire Instruction,它是指Trigger在既定的觸發(fā)時(shí)間由于系統(tǒng)故障原因?qū)е聸]有被正確觸發(fā),待系統(tǒng)恢復(fù)正常運(yùn)行后的補(bǔ)救行為。Misfire Instruction主要有以下幾個(gè)選項(xiàng):
? MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY(執(zhí)行錯(cuò)過的任務(wù))。
? MISFIRE_INSTRUCTION_DO_NOTHING(等待下次Cron觸發(fā)頻率到達(dá)時(shí)執(zhí)行任務(wù))。
? MISFIRE_INSTRUCTION_FIRE_NOW(立刻觸發(fā)一次任務(wù))。
(6)org.Quartz.JobStore
主要用于存儲Job和Trigger的信息,一般選擇RAMJobStore或JDBCJobStore。
(7)org.Quartz.core.QuartzScheduler
定義Trigger和Job之后,需要通過Scheduler將二者關(guān)聯(lián),QuartzScheduler是Scheduler的一個(gè)關(guān)鍵實(shí)現(xiàn),開發(fā)者可以通過QuartzScheduler將Job和Trigger進(jìn)行關(guān)聯(lián)注冊,代碼如下:

此處的Job并非Job實(shí)例,而是JobDetail實(shí)例,至此整個(gè)定義流程完畢,Job將會被指定于Trigger定義的時(shí)間運(yùn)行,最后只需執(zhí)行scheduler.start()啟動(dòng)任務(wù)即可。
Quartz運(yùn)行期的邏輯關(guān)系如圖3-97所示。

圖3-97 Quartz運(yùn)行期的邏輯關(guān)系
3.7.1.2 Cron表達(dá)式
Cron是一種簡單且高效的任務(wù)時(shí)間表達(dá)式,Quartz對基于Cron的觸發(fā)器做了很好的支持。Quartz的Cron表達(dá)式和原生的UNIX下的Cron表達(dá)式有所不同,例如Quartz的Cron表達(dá)式可以精確到秒,而UNIX只能精確到分鐘,Quartz的Cron表達(dá)式格式如圖3-98所示。

圖3-98 Cron表達(dá)式
Cron表達(dá)式各位置所允許的取值范圍如表3-6所示。
表3-6 Cron表達(dá)式位置含義及語法

表3-6中包含的特殊字符含義如下:
? 星號(*):可用在所有字段中,表示對應(yīng)時(shí)間域的每一個(gè)時(shí)刻,例如,*在分鐘字段時(shí),表示“每分鐘”。
? 問號(?):該字符只在日期和星期字段中使用,它通常指定為“無意義的值”,相當(dāng)于占位符。
? *和?的區(qū)別:二者都表示不確定的值,但各自表達(dá)的含義差異很大。首先,“?”只能用于日期和星期,這是因?yàn)樵诖蠖鄶?shù)情況下(特別是在不知道年份時(shí))是無法同時(shí)定義準(zhǔn)確的日期和星期幾的。其次,“*”可以出現(xiàn)很多次,但是“?”只能出現(xiàn)一次,表示日期和星期最多一個(gè)無意義。
? 減號(-):表達(dá)一個(gè)范圍,如在小時(shí)字段中使用“10-12”,則表示從10點(diǎn)到12點(diǎn),即10點(diǎn)、11點(diǎn)、12點(diǎn)。
? 逗號(,):表達(dá)一個(gè)列表值,如在星期字段中使用MON、WED、FRI,則表示星期一、星期三和星期五。
? 斜杠(/):x/y表達(dá)一個(gè)等步長序列,x為起始值,y為增量步長值。如在分鐘字段中使用0/15,則表示為0、15、30和45s,而5/15在分鐘字段中表示5、20、35、50,你也可以使用*/y,它等同于0/y。
? L:該字符只在日期和星期字段中使用,代表Last的意思,但它在兩個(gè)字段中意思不同。L在日期字段中,表示這個(gè)月份的最后一天,如一月的31號,非閏年二月的28號;如果L用在星期中,則表示星期六,等同于7。但是,如果L出現(xiàn)在星期字段里,而且在前面有一個(gè)數(shù)值X,則表示“這個(gè)月的最后X天”,例如,6L表示該月最后的星期五。
? W:該字符只能出現(xiàn)在日期字段里,是對前導(dǎo)日期的修飾,表示離該日期最近的工作日。例如15W表示離該月15號最近的工作日,如果該月15號是星期六,則匹配14號星期五;如果15日是星期日,則匹配16號星期一;如果15號是星期二,那結(jié)果就是15號星期二。但必須注意關(guān)聯(lián)的匹配日期不能夠跨月,如你指定1W,如果1號是星期六,結(jié)果匹配的是3號星期一,而非上個(gè)月最后的那天。W字符串只能指定單一日期,而不能指定日期范圍。
? LW組合:在日期字段可以組合使用LW,它的意思是當(dāng)月的最后一個(gè)工作日。
? 井號(#):該字符只能在星期字段中使用,表示當(dāng)月某個(gè)工作日。如6#3表示當(dāng)月的第三個(gè)星期五(6表示星期五,#3表示當(dāng)前的第三個(gè)),而4#5表示當(dāng)月的第五個(gè)星期三,假設(shè)當(dāng)月沒有第五個(gè)星期三,則忽略不觸發(fā)。
? C:該字符只在日期和星期字段中使用,代表Calendar的意思。它的意思是計(jì)劃所關(guān)聯(lián)的日期,如果日期沒有被關(guān)聯(lián),則相當(dāng)于日歷中所有日期。例如5C在日期字段中就相當(dāng)于日歷5日以后的第一天。1C在星期字段中相當(dāng)于星期日后的第一天。
注意:以上字符包括月份和星期都不是大小寫敏感的。
3.7.2 Spring Batch
3.7.2.1 Spring Batch簡介
通過分析Quartz架構(gòu),我們可以知道Quartz的重點(diǎn)在于定義何時(shí)執(zhí)行一個(gè)計(jì)劃任務(wù),并保證在正確的時(shí)間觸發(fā)該任務(wù),但是Quartz架構(gòu)對于如何實(shí)現(xiàn)任務(wù)的細(xì)節(jié)并沒有定義任何規(guī)范。因此,Spring社區(qū)創(chuàng)建了Spring Batch來完善任務(wù)處理流程,Spring Batch所解決的問題是“如何執(zhí)行任務(wù)”。Spring Batch的主要模塊及工作流程如圖3-99所示。

圖3-99 Spring Batch的主要模塊及工作流程
如圖3-99所示,圖中各組件描述如下:
? Job
在Spring Batch中,Job是一個(gè)包含了一系列流程的執(zhí)行單元。
? JobLauncher
一個(gè)啟動(dòng)Job的入口,用戶可以直接使用JobLauncher來啟動(dòng)需要啟動(dòng)的Job。
? JobRepository
用于存儲執(zhí)行Job的相關(guān)信息
? Step
Job的一個(gè)執(zhí)行單元,一個(gè)Job可以包含多個(gè)Step,Step支持Chunk或Tasklet模式。
? Item
一條數(shù)據(jù)源中的記錄。
? Tasklet
采用Tasklet模式意味著在一個(gè)步驟內(nèi)只執(zhí)行一個(gè)任務(wù),Tasklet模式的特點(diǎn)是每個(gè)步驟都需要將數(shù)據(jù)源的所有Item全部處理之后才能執(zhí)行下一個(gè)步驟。
? Chunks
在Chunks模式下,每個(gè)步驟不需要處理全部Item,只用處理一個(gè)恒定數(shù)量的Item集合,處理完一個(gè)Chunk的數(shù)據(jù)后就可以執(zhí)行下一個(gè)步驟。
? Item Reader
用于從數(shù)據(jù)源中讀取Item的組件。
? Item Processor
將讀取的Item進(jìn)行一系列處理,比如按照要求過濾結(jié)果集或改變返回?cái)?shù)據(jù)的格式。
? Item Rriter
用于將Item寫入數(shù)據(jù)源的組件。
Spring Batch的運(yùn)行期流程如圖3-100所示。

圖3-100 Spring Batch運(yùn)行期流程圖
3.7.2.2 集成Quartz和Spring Batch
Quartz擅長做任務(wù)計(jì)劃調(diào)度,而Spring Batch擅長管控任務(wù)的處理細(xì)節(jié),如果將二者結(jié)合在一起,能否產(chǎn)生1+1>2的效果呢?本節(jié),我們就借助Quartz+Spring Batch的組合為coupon-user-service添加任務(wù)調(diào)度功能。
coupon-user-service項(xiàng)目需要一個(gè)新功能,統(tǒng)計(jì)各種不同優(yōu)惠券的使用情況,在每天晚上11點(diǎn)生成報(bào)表,利用Spring Boot集成Quartz和Spring Batch從而實(shí)現(xiàn)該場景的步驟如下:
第一步,在coupon-user-service項(xiàng)目的pom.xml文件中添加相關(guān)依賴,具體代碼如下:

第二步,創(chuàng)建一個(gè)用于做統(tǒng)計(jì)的輔助類,命名為Counter,具體代碼如下:

第三步,創(chuàng)建QuartzJobLauncher類,QuartzJobLauncher繼承了QuartzJobBean類(QuartzJobBean實(shí)現(xiàn)了org.Quartz.Job)。QuartzJobLauncher就像一座橋梁,在Quartz和Spring Batch之間建立起了連接。QuartzJobLauncher具體代碼如下:

第四步,創(chuàng)建Scheduler相關(guān)配置類,具體代碼如下:


第五步,創(chuàng)建一個(gè)簡單的ItemProcessor實(shí)例,完成統(tǒng)計(jì)工作,具體代碼如下:


最后一步,創(chuàng)建BatchConfiguration類,在這個(gè)類中約定任務(wù)中每個(gè)步驟的執(zhí)行順序,具體代碼如下:



我們將Quartz和Spring Batch的優(yōu)勢組合在一起,定義了一種全新的定時(shí)任務(wù)框架執(zhí)行方式。
- scikit-learn Cookbook
- Git Version Control Cookbook
- C++程序設(shè)計(jì)(第3版)
- JavaFX Essentials
- Android Application Development Cookbook(Second Edition)
- Java編程指南:基礎(chǔ)知識、類庫應(yīng)用及案例設(shè)計(jì)
- AngularJS深度剖析與最佳實(shí)踐
- Data Analysis with IBM SPSS Statistics
- 鋒利的SQL(第2版)
- 手把手教你學(xué)C語言
- Python數(shù)據(jù)分析從0到1
- C語言程序設(shè)計(jì)實(shí)驗(yàn)指導(dǎo) (第2版)
- HTML5從入門到精通(第4版)
- Visual C#.NET Web應(yīng)用程序設(shè)計(jì)
- Mastering Elixir