一誉帅、前情
工作業(yè)務(wù)需要淀散,做一個定時消息發(fā)送的通用功能,首先想到了quartz這個老牌大殺器
二蚜锨、 團隊為什么選擇他档插?
- 1.java編寫的開源作業(yè)調(diào)度框架設(shè)計,用于J2SE和J2EE應(yīng)用方便集成亚再。
- 2.資歷夠老,創(chuàng)立于1998年,比struts1還早郭膛,而且一直在更新(24 Sept 2013: Quartz 2.2.1 Released),文檔齊全。
- 3.設(shè)計清晰簡單:核心概念scheduler,trigger,job,jobDetail,listener,calendar
- 4.支持集群:org.quartz.jobStore.isClustered 最重要的一點原因是quartz是支持集群的氛悬。不然JDK自帶Timer就可以實現(xiàn)相同的功能则剃。
- 5.支持任務(wù)恢復(fù):requestsRecovery
- 6.普及面很廣,JAVA開發(fā)人員比較熟悉如捅。
- 7.Apache2.0開源協(xié)議棍现,允許代碼修改,再商業(yè)發(fā)布镜遣。
三己肮、基本概念概述
最基本(最重要)名詞解釋
基本元素 | 名詞解釋 |
---|---|
scheduler | 任務(wù)調(diào)度器 |
trigger | 觸發(fā)器,用于定義任務(wù)調(diào)度時間規(guī)則 |
job | 任務(wù)悲关,即被調(diào)度的任務(wù) |
misfire | 錯過的谎僻,指本來應(yīng)該被執(zhí)行但實際沒有被執(zhí)行的任務(wù)調(diào)度 |
細(xì)枝末節(jié)
Job
是一個接口,只有一個方法void execute(JobExecutionContext context)寓辱,開發(fā)者實現(xiàn)該接口定義運行任務(wù)艘绍,JobExecutionContext類提供了調(diào)度上下文的各種信息。Job運行時的信息保存在JobDataMap實例中秫筏;
JobDetail
Quartz在每次執(zhí)行Job時诱鞠,都重新創(chuàng)建一個Job實例挎挖,所以它不直接接受一個Job的實例,相反它接收一個Job實現(xiàn)類般甲,以便運行時通過newInstance()的反射機制實例化Job肋乍。因此需要通過一個類來描述Job的實現(xiàn)類及其它相關(guān)的靜態(tài)信息,如Job名字敷存、描述墓造、關(guān)聯(lián)監(jiān)聽器等信息,JobDetail承擔(dān)了這一角色锚烦。Trigger
是一個類觅闽,描述 主要有SimpleTrigger和CronTrigger這兩個子類涮俄。當(dāng)僅需觸發(fā)一次或者以固定時間間隔周期執(zhí)行蛉拙,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達(dá)式定義出各種復(fù)雜時間規(guī)則的調(diào)度方案:如每早晨9:00執(zhí)行彻亲,周一孕锄、周三、周五下午5:00執(zhí)行等苞尝;Calendar
org.quartz.Calendar和java.util.Calendar不同畸肆,它是一些日歷特定時間點的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個日歷時間點,無特殊說明后面的Calendar即指org.quartz.Calendar)宙址。一個Trigger可以和多個Calendar關(guān)聯(lián)轴脐,以便排除或包含某些時間點。假設(shè)抡砂,我們安排每周星期一早上10:00執(zhí)行任務(wù)大咱,但是如果碰到法定的節(jié)日,任務(wù)則不執(zhí)行注益,這時就需要在Trigger觸發(fā)機制的基礎(chǔ)上使用Calendar進行定點排除碴巾。Scheduler
代表一個Quartz的獨立運行容器,Trigger和JobDetail可以注冊到Scheduler中丑搔,兩者在Scheduler中擁有各自的組及名稱厦瓢,組及名稱是Scheduler查找定位容器中某一對象的依據(jù),Trigger的組及名稱必須唯一低匙,JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因為它們是不同類型的)碳锈。Scheduler定義了多個接口方法顽冶,允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail。ThreadPool
Scheduler使用一個線程池作為任務(wù)運行的基礎(chǔ)設(shè)施售碳,任務(wù)通過共享線程池中的線程提高運行效率强重。之間關(guān)系
Scheduler可以將Trigger綁定到某一JobDetail中绞呈,這樣當(dāng)Trigger觸發(fā)時,對應(yīng)的Job就被執(zhí)行间景。一個Job可以對應(yīng)多個Trigger佃声,但一個Trigger只能對應(yīng)一個Job√纫可以通過SchedulerFactory創(chuàng)建一個Scheduler實例圾亏。Scheduler擁有一個SchedulerContext,它類似于ServletContext封拧,保存著Scheduler上下文信息志鹃,Job和Trigger都可以訪問SchedulerContext內(nèi)的信息。 SchedulerContext為保存和獲取數(shù)據(jù)提供了多個put()和getXxx()的方法∨跎迹可以通過Scheduler# getContext()獲取對應(yīng)的SchedulerContext實例陕见;
四、 設(shè)計分析
基本概念再分析
quartz.properties文件
Quartz有一個叫做quartz.properties的配置文件味抖,它允許你修改框架運行時環(huán)境评甜。缺省是使用Quartz.jar里面的quartz.properties文件。當(dāng)然非竿,你應(yīng)該創(chuàng)建一個quartz.properties文件的副本并且把它放入你工程的classes目錄中以便類裝載器找到它蜕着。
一旦將Quartz.jar文件和第三方庫加到自己的工程里面并且quartz.properties文件在工程的classes目錄中,就可以創(chuàng)建作業(yè)了红柱。然而承匣,在做這之前,我們暫且回避一下先簡短討論一下Quartz架構(gòu)锤悄。
Quartz內(nèi)部架構(gòu)
在規(guī)模方面韧骗,Quartz跟大多數(shù)開源框架類似。大約有300個Java類和接口零聚,并被組織到12個包中袍暴。這可以和Apache Struts把大約325個類和接口以及組織到11個包中相比。盡管規(guī)模幾乎不會用來作為衡量框架質(zhì)量的一個特性隶症,但這里的關(guān)鍵是quarts內(nèi)含很多功能政模,這些功能和特性集是否成為、或者應(yīng)該成為評判一個開源或非開源框架質(zhì)量的因素蚂会。
Quartz調(diào)度器
Quartz框架的核心是調(diào)度器淋样。調(diào)度器負(fù)責(zé)管理Quartz應(yīng)用運行時環(huán)境。調(diào)度器不是靠自己做所有的工作胁住,而是依賴框架內(nèi)一些非常重要的部件趁猴。Quartz不僅僅是線程和線程管理刊咳。為確保可伸縮性儡司,Quartz采用了基于多線程的架構(gòu)娱挨。
啟動時,框架初始化一套worker線程捕犬,這套線程被調(diào)度器用來執(zhí)行預(yù)定的作業(yè)跷坝。這就是Quartz怎樣能并發(fā)運行多個作業(yè)的原理。Quartz依賴一套松耦合的線程池管理部件來管理線程環(huán)境或听。本文中探孝,我們會多次提到線程池管理,但Quartz里面的每個對象是可配置的或者是可定制的誉裆。所以顿颅,例如,如果你想要插進自己線程池管理設(shè)施足丢,我猜你一定能粱腻!
作業(yè)
用Quartz的行話講,作業(yè)是一個執(zhí)行任務(wù)的簡單Java類斩跌。任務(wù)可以是任何Java代碼绍些。只需你并且在出現(xiàn)嚴(yán)重錯誤情況下拋出JobExecutionException異常即可。
Job接口包含唯一的一個方法execute()耀鸦,作業(yè)從這里開始執(zhí)行柬批。一旦實現(xiàn)了Job接口和execute()方法,當(dāng)Quartz確定該是作業(yè)運行的時候袖订,它將調(diào)用你的作業(yè)氮帐。Execute()方法內(nèi)就完全是你要做的事情。
作業(yè)管理和存儲
作業(yè)一旦被調(diào)度洛姑,調(diào)度器需要記住并且跟蹤作業(yè)和它們的執(zhí)行次數(shù)上沐。如果你的作業(yè)是30分鐘后或每30秒調(diào)用,這不是很有用楞艾。事實上参咙,作業(yè)執(zhí)行需要非常準(zhǔn)確和即時調(diào)用在被調(diào)度作業(yè)上的execute()方法。Quartz通過一個稱之為作業(yè)存儲(JobStore)的概念來做作業(yè)存儲和管理硫眯。
有效作業(yè)存儲
Quartz提供兩種基本作業(yè)存儲類型蕴侧。第一種類型叫做RAMJobStore,它利用通常的內(nèi)存來持久化調(diào)度程序信息两入。這種作業(yè)存儲類型最容易配置净宵、構(gòu)造和運行。對許多應(yīng)用來說,這種作業(yè)存儲已經(jīng)足夠了塘娶。
然而,因為調(diào)度程序信息是存儲在被分配給JVM的內(nèi)存里面痊夭,所以刁岸,當(dāng)應(yīng)用程序停止運行時,所有調(diào)度信息將被丟失她我。如果你需要在重新啟動之間持久化調(diào)度信息虹曙,則將需要第二種類型的作業(yè)存儲。
第二種類型的作業(yè)存儲實際上提供兩種不同的實現(xiàn)番舆,但兩種實現(xiàn)一般都稱為JDBC作業(yè)存儲酝碳。兩種JDBC作業(yè)存儲都需要JDBC驅(qū)動程序和后臺數(shù)據(jù)庫來持久化調(diào)度程序信息。這兩種類型的不同在于你是否想要控制數(shù)據(jù)庫事務(wù)或這釋放控制給應(yīng)用服務(wù)器例如BEA's WebLogic或Jboss恨狈。(這類似于J2EE領(lǐng)域中疏哗,Bean管理的事務(wù)和和容器管理事務(wù)之間的區(qū)別)這兩種JDBC作業(yè)存儲是:
· JobStoreTX:當(dāng)你想要控制事務(wù)或工作在非應(yīng)用服務(wù)器環(huán)境中是使用
· JobStoreCMT:當(dāng)你工作在應(yīng)用服務(wù)器環(huán)境中和想要容器控制事務(wù)時使用。
JDBC作業(yè)存儲為需要調(diào)度程序維護調(diào)度信息的用戶而設(shè)計禾怠。
作業(yè)和觸發(fā)器
Quartz設(shè)計者做了一個設(shè)計選擇來從調(diào)度分離開作業(yè)返奉。Quartz中的觸發(fā)器用來告訴調(diào)度程序作業(yè)什么時候觸發(fā)÷鹗希框架提供了一把觸發(fā)器類型芽偏,但兩個最常用的是SimpleTrigger和CronTrigger。SimpleTrigger為需要簡單打火調(diào)度而設(shè)計弦讽。
SimpleTrigger
典型地污尉,如果你需要在給定的時間和重復(fù)次數(shù)或者兩次打火之間等待的秒數(shù)打火一個作業(yè),那么SimpleTrigger適合你往产。另一方面被碗,如果你有許多復(fù)雜的作業(yè)調(diào)度,那么或許需要CronTrigger捂齐。
CronTrigger
CronTrigger是基于Calendar-like調(diào)度的蛮放。當(dāng)你需要在除星期六和星期天外的每天上午10點半執(zhí)行作業(yè)時,那么應(yīng)該使用CronTrigger奠宜。正如它的名字所暗示的那樣包颁,CronTrigger是基于Unix克隆表達(dá)式的。
作為一個例子压真,下面的Quartz克隆表達(dá)式將在星期一到星期五的每天上午10點15分執(zhí)行一個作業(yè)娩嚼。
0 15 10 ? * MON-FRI
下面的表達(dá)式
0 15 10 ? * 6L 2002-2005
將在2002年到2005年的每個月的最后一個星期五上午10點15分執(zhí)行作業(yè)。你不可能用SimpleTrigger來做這些事情滴肿。你可以用兩者之中的任何一個岳悟,但哪個跟合適則取決于你的調(diào)度需要。
錯過觸發(fā)(misfire)
trigger還有一個重要的屬性misfire;如果scheduler關(guān)閉了贵少,或者Quartz線程池中沒有可用的線程來執(zhí)行job呵俏,此時持久性的trigger就會錯過(miss)其觸發(fā)時間,即錯過觸發(fā)(misfire)滔灶。不同類型的trigger普碎,有不同的misfire機制。它們默認(rèn)都使用“智能機制(smart policy)”录平,即根據(jù)trigger的類型和配置動態(tài)調(diào)整行為麻车。當(dāng)scheduler啟動的時候,查詢所有錯過觸發(fā)(misfire)的持久性trigger斗这。然后根據(jù)它們各自的misfire機制更新trigger的信息动猬。當(dāng)你在項目中使用Quartz時,你應(yīng)該對各種類型的trigger的misfire機制都比較熟悉表箭,這些misfire機制在JavaDoc中有說明赁咙。關(guān)于misfire機制的細(xì)節(jié),會在講到具體的trigger時作介紹免钻。
調(diào)度一個作業(yè)
讓我們通過看一個例子來進入實際討論⌒蚰浚現(xiàn)假定你管理一個部門,無論何時候客戶在它的FTP服務(wù)器上存儲一個文件伯襟,都得用電子郵件通知它猿涨。我們的作業(yè)將用FTP登陸到遠(yuǎn)程服務(wù)器并下載所有找到的文件。
然后姆怪,它將發(fā)送一封含有找到和下載的文件數(shù)量的電子郵件叛赚。這個作業(yè)很容易就幫助人們整天從手工執(zhí)行這個任務(wù)中解脫出來,甚至連晚上都無須考慮稽揭。我們可以設(shè)置作業(yè)循環(huán)不斷地每60秒檢查一次俺附,而且工作在7×24模式下。這就是Quartz框架完全的用途溪掀。
首先創(chuàng)建一個Job類事镣,將執(zhí)行FTP和Email邏輯。下例展示了Quartz的Job類揪胃,它實現(xiàn)了org.quartz.Job接口璃哟。
用調(diào)度器調(diào)用作業(yè)
首先創(chuàng)建一個作業(yè),但為使作業(yè)能被調(diào)度器調(diào)用喊递,你得向調(diào)度程序說明你的作業(yè)的調(diào)用時間和頻率随闪。這個事情由與作業(yè)相關(guān)的觸發(fā)器來完成。因為我們僅僅對大約每60秒循環(huán)調(diào)用作業(yè)感興趣骚勘,所以打算使用SimpleTrigger铐伴。
作業(yè)和觸發(fā)器通過Quartz調(diào)度器接口而被調(diào)度撮奏。我們需要從調(diào)度器工廠類取得一個調(diào)度器的實例。最容易的辦法是調(diào)用StdSchedulerFactory這個類上的靜態(tài)方法getDefaultScheduler()当宴。
使用Quartz框架畜吊,你需要調(diào)用start()方法來啟動調(diào)度器。例3的代碼遵循了大多數(shù)Quartz應(yīng)用的一般模式:創(chuàng)建一個或多個作業(yè)户矢,創(chuàng)建和設(shè)置觸發(fā)器定拟,用調(diào)度器調(diào)度作業(yè)和觸發(fā)器,啟動調(diào)度器逗嫡。
編程調(diào)度同聲明性調(diào)度
我們通過編程的方法調(diào)度我們的ScanFTPSiteJob作業(yè)。就是說株依,我們用Java代碼來設(shè)置作業(yè)和觸發(fā)器驱证。Quartz框架也支持在xml文件里面申明性的設(shè)置作業(yè)調(diào)度。申明性方法允許我們更快速地修改哪個作業(yè)什么時候被執(zhí)行恋腕。
Quartz框架有一個插件抹锄,這個插件負(fù)責(zé)讀取xml配置文件。xml配置文件包含了關(guān)于啟動Quartz應(yīng)用的作業(yè)和觸發(fā)器信息荠藤。所有xml文件中的作業(yè)連同相關(guān)的觸發(fā)器都被加進調(diào)度器伙单。你仍然需要編寫作業(yè)類,但配置那些作業(yè)類的調(diào)度器則非常動態(tài)化哈肖。你可以將xml文件中的元素跟例3代碼作個比較吻育,它們從概念上來看是相同的。使用申明性方法的好處是維護變得極其簡單淤井,只需改變xml配置文件和重新啟動Quartz應(yīng)用即可布疼。無須修改代碼,無須重新編譯币狠,無須重新部署游两。
有狀態(tài)和無狀態(tài)作業(yè)
作業(yè)到是無狀態(tài)的。這意味著在兩次作業(yè)執(zhí)行之間漩绵,不會去維護作業(yè)執(zhí)行時JobDataMap的狀態(tài)改變贱案。如果你需要能增、刪止吐,改JobDataMap的值宝踪,而且能讓作業(yè)在下次執(zhí)行時能看到這個狀態(tài)改變,則需要用Quartz有狀態(tài)作業(yè)碍扔。
Quartz有狀態(tài)作業(yè)實現(xiàn)了org.quartz.StatefulJob接口肴沫。
無狀態(tài)和有狀態(tài)作業(yè)的關(guān)鍵不同是有狀態(tài)作業(yè)在每次執(zhí)行時只有一個實例。大多數(shù)情況下蕴忆,有狀態(tài)的作業(yè)不回帶來大的問題颤芬。然而,如果你有一個需要頻繁執(zhí)行的作業(yè)或者需要很長時間才能完成的作業(yè),那么有狀態(tài)作業(yè)可能給你帶來伸縮性問題站蝠。
監(jiān)聽器和插件
每個人都喜歡監(jiān)聽和插件汰具。今天,幾乎下載任何開源框架菱魔,你必定會發(fā)現(xiàn)支持這兩個概念留荔。監(jiān)聽是你創(chuàng)建的Java類,當(dāng)關(guān)鍵事件發(fā)生時會收到框架的回調(diào)澜倦。例如聚蝶,當(dāng)一個作業(yè)被調(diào)度、沒有調(diào)度或觸發(fā)器終止和不再打火時藻治,這些都可以通過設(shè)置來來通知你的監(jiān)聽器碘勉。Quartz框架包含了調(diào)度器監(jiān)聽、作業(yè)和觸發(fā)器監(jiān)聽桩卵。你可以配置作業(yè)和觸發(fā)器監(jiān)聽為全局監(jiān)聽或者是特定于作業(yè)和觸發(fā)器的監(jiān)聽验靡。
一旦你的一個具體監(jiān)聽被調(diào)用,你就能使用這個技術(shù)來做一些你想要在監(jiān)聽類里面做的事情雏节。例如胜嗓,你如果想要在每次作業(yè)完成時發(fā)送一個電子郵件,你可以將這個邏輯寫進作業(yè)里面钩乍,也可以JobListener里面辞州。寫進JobListener的方式強制使用松耦合有利于設(shè)計上做到更好。
Quartz插件是一個新的功能特性寥粹,無須修改Quartz源碼便可被創(chuàng)建和添加進Quartz框架孙技。他為想要擴展Quartz框架又沒有時間提交改變給Quartz開發(fā)團隊和等待新版本的開發(fā)人員而設(shè)計。如果你熟悉Struts插件的話排作,那么完全可以理解Quartz插件的使用牵啦。
與其Quartz提供一個不能滿足你需要的有限擴展點,還不如通過使用插件來擁有可修整的擴展點妄痪。
集群Quartz應(yīng)用
Quartz應(yīng)用能被集群哈雏,是水平集群還是垂直集群取決于你自己的需要。集群提供以下好處:
· 伸縮性
· 高可用性
· 負(fù)載均衡
目前衫生,Quartz只能借助關(guān)系數(shù)據(jù)庫和JDBC作業(yè)存儲支持集群裳瘪。將來的版本這個制約將消失并且用RAMJobStore集群將是可能的而且將不需要數(shù)據(jù)庫的支持。
尾語
未完待續(xù)之 配置pian