最近項目中經常用到隊列和定時任務及線程的整合應用,涉及的場景是當多人訪問系統時需要回調客戶系統處理結果時,如何減少服務器壓力並能處理業務需求,這裏用到了隊列減少服務器壓力加入定時任務發送機制,使用的是Spring框架整合 Quartz框架實現的定時任務,因此學習了一下Quartz的前因後果 在此記錄學習一下。
一、引入
你曾經需要應用執行一個任務嗎?這個任務每天或每週星期二晚上11:30,或許僅僅每個月的最後一天執行。一個自動執行而無須干預的任務在執行過程中如果發生一個嚴重錯誤,應用能夠知到其執行失敗並嘗試重新執行嗎?你和你的團隊是用Java編程嗎?如果這些問題中任何一個你回答是,那麼你應該使用Quartz調度器。
二、爲什麼研發團隊會選擇quartz
java編寫的開源作業調度框架設計,用於J2SE和J2EE應用方便集成。
資歷夠老,創立於1998年,比struts1還早,而且一直在更新(24 Sept 2013: Quartz 2.2.1 Released),文檔齊全。
設計清晰簡單:核心概念scheduler,trigger,job,jobDetail,listener,calendar
支持集羣:org.quartz.jobStore.isClustered 最重要的一點原因是quartz是支持集羣的。不然JDK自帶Timer就可以實現相同的功能。
支持任務恢復:requestsRecovery
普及面很廣,JAVA開發人員比較熟悉。
Apache2.0開源協議,允許代碼修改,再商業發佈。
三、如何使定時任務的開發方便,易於管理。
阿里開源項目easySchedule在quartz集羣的基礎上搭建了一個簡單的管理平臺。解決了可視化、易配置、統一監控告警功能。
實現調度與執行的分離,使任務不需要再去關注定時,只需要實現任務接口即可。
調度通過HTTP來調用執行任務。
easySchedule系統特點:
1、 Server和Client分別支持集羣和分佈式部署
2、 任務的執行與調度分離
3、 可視化管理所有任務
4、 任務狀態持久化於DB
5、 完善的日誌跟蹤和告警策略
6、 任務支持異步調度
7、 靈活支持各種自定義任務,擴展方便
這裏後期會學習
四、quartz核心概念
1、Job:是一個接口,只有一個方法void execute(JobExecutionContext context),開發者實現該接口定義運行任務,JobExecutionContext類提供了調度上下文的各種信息。Job運行時的信息保存在JobDataMap實例中;
2、JobDetail:Quartz在每次執行Job時,都重新創建一個Job實例,所以它不直接接受一個Job的實例,相反它接收一個Job實現類,以便運行時通過newInstance()的反射機制實例化Job。因此需要通過一個類來描述Job的實現類及其它相關的靜態信息,如Job名字、描述、關聯監聽器等信息,JobDetail承擔了這一角色。
3、Trigger:是一個類,描述觸發Job執行的時間觸發規則。主要有SimpleTrigger和CronTrigger這兩個子類。當僅需觸發一次或者以固定時間間隔週期執行,SimpleTrigger是最適合的選擇;而CronTrigger則可以通過Cron表達式定義出各種複雜時間規則的調度方案:如每早晨9:00執行,週一、週三、週五下午5:00執行等;
4、Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日曆特定時間點的集合(可以簡單地將org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一個日曆時間點,無特殊說明後面的Calendar即指org.quartz.Calendar)。一個Trigger可以和多個Calendar關聯,以便排除或包含某些時間點。假設,我們安排每週星期一早上10:00執行任務,但是如果碰到法定的節日,任務則不執行,這時就需要在Trigger觸發機制的基礎上使用Calendar進行定點排除。
5、Scheduler:代表一個Quartz的獨立運行容器,Trigger和JobDetail可以註冊到Scheduler中,兩者在Scheduler中擁有各自的組及名稱,組及名稱是Scheduler查找定位容器中某一對象的依據,Trigger的組及名稱必須唯一,JobDetail的組和名稱也必須唯一(但可以和Trigger的組和名稱相同,因爲它們是不同類型的)。Scheduler定義了多個接口方法,允許外部通過組及名稱訪問和控制容器中Trigger和JobDetail。
6、 Scheduler可以將Trigger綁定到某一JobDetail中,這樣當Trigger觸發時,對應的Job就被執行。一個Job可以對應多個Trigger,但一個Trigger只能對應一個Job。可以通過SchedulerFactory創建一個Scheduler實例。Scheduler擁有一個SchedulerContext,它類似於ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以訪問SchedulerContext內的信息。SchedulerContext內部通過一個Map,以鍵值對的方式維護這些上下文數據,SchedulerContext爲保存和獲取數據提供了多個put()和getXxx()的方法。可以通過Scheduler# getContext()獲取對應的SchedulerContext實例;
7、ThreadPool:Scheduler使用一個線程池作爲任務運行的基礎設施,任務通過共享線程池中的線程提高運行效率。
Quartz內部架構
在規模方面,Quartz跟大多數開源框架類似。大約有300個Java類和接口,並被組織到12個包中。這可以和Apache Struts把大約325個類和接口以及組織到11個包中相比。儘管規模幾乎不會用來作爲衡量框架質量的一個特性,但這裏的關鍵是quarts內含很多功能,這些功能和特性集是否成爲、或者應該成爲評判一個開源或非開源框架質量的因素。
Quartz調度器
Quartz框架的核心是調度器。調度器負責管理Quartz應用運行時環境。調度器不是靠自己做所有的工作,而是依賴框架內一些非常重要的部件。Quartz不僅僅是線程和線程管理。爲確保可伸縮性,Quartz採用了基於多線程的架構。
啓動時,框架初始化一套worker線程,這套線程被調度器用來執行預定的作業。這就是Quartz怎樣能併發運行多個作業的原理。Quartz依賴一套鬆耦合的線程池管理部件來管理線程環境。本文中,我們會多次提到線程池管理,但Quartz裏面的每個對象是可配置的或者是可定製的。所以,例如,如果你想要插進自己線程池管理設施,我猜你一定能!
作業和觸發器
Quartz設計者做了一個設計選擇來從調度分離開作業。Quartz中的觸發器用來告訴調度程序作業什麼時候觸發。框架提供了一把觸發器類型,但兩個最常用的是SimpleTrigger和CronTrigger。SimpleTrigger爲需要簡單打火調度而設計。
典型地,如果你需要在給定的時間和重複次數或者兩次打火之間等待的秒數打火一個作業,那麼SimpleTrigger適合你。另一方面,如果你有許多複雜的作業調度,那麼或許需要CronTrigger。
Cron表達式
CronTrigger是基於Calendar-like調度的。當你需要在除星期六和星期天外的每天上午10點半執行作業時,那麼應該使用CronTrigger。正如它的名字所暗示的那樣,CronTrigger是基於Unix克隆表達式的。
作爲一個例子,下面的Quartz克隆表達式將在星期一到星期五的每天上午10點15分執行一個作業。
0 15 10 ? * MON-FRI
下面的表達式
0 15 10 ? * 6L 2002-2005
將在2002年到2005年的每個月的最後一個星期五上午10點15分執行作業。你不可能用SimpleTrigger來做這些事情。你可以用兩者之中的任何一個,但哪個跟合適則取決於你的調度需要。
特殊字符的含義
錯過觸發(misfire)
trigger還有一個重要的屬性misfire;如果scheduler關閉了,或者Quartz線程池中沒有可用的線程來執行job,此時持久性的trigger就會錯過(miss)其觸發時間,即錯過觸發(misfire)。不同類型的trigger,有不同的misfire機制。它們默認都使用“智能機制(smart policy)”,即根據trigger的類型和配置動態調整行爲。當scheduler啓動的時候,查詢所有錯過觸發(misfire)的持久性trigger。然後根據它們各自的misfire機制更新trigger的信息。當你在項目中使用Quartz時,你應該對各種類型的trigger的misfire機制都比較熟悉,這些misfire機制在JavaDoc中有說明。關於misfire機制的細節,會在講到具體的trigger時作介紹。
五、Spring整合Quartz
定時任務兩種方式,Spring很好的封裝使用Quartz的細節,第一種方式是利用SPring封裝的Quartz類進行特定方法的實現,第二種是通過透明的使用Quartz達到定時任務開發的目的,總體說第二種對開發人員更方便!
配置Spring的任務調度抽象層簡化了任務調度,在Quartz的基礎上提供了更好的調度對象。Spring使用Quartz框架來完成任務調度,創建Quartz的作業Bean(JobDetail),有一下兩種方法:
1:利用JobDetailBean包裝QuartzJobBean子類(即Job類)的實例。
2:利用MethodInvokingJobDetailFactoryBean工廠Bean包裝普通的Java對象(即Job類)。
說明:
1:採用第一種方法 創建job類,一定要繼承QuartzJobBean ,實現 executeInternal(JobExecutionContext
jobexecutioncontext)方法,此方法就是被調度任務的執行體,然後將此Job類的實例直接配置到JobDetailBean中即可。這種方法和在普通的Quartz編程中是一樣的。
2:採用第二種方法 創建Job類,無須繼承父類,直接配置MethodInvokingJobDetailFactoryBean即可。但需要指定一下兩個屬性:
targetObject:指定包含任務執行體的Bean實例。
targetMethod:指定將指定Bean實例的該方法包裝成任務的執行體。
這裏我們介紹第二種
Spring的配置文件:
在需要添加Spring 配置文件的Spring.xml/application.xml添加
配置自動調度的包和定時開關
配置調度和註解調度
在web.xml配置裏添加啓動時要掃描的配置文件和監聽