聲明
本文歡迎轉載,原文地址:http://www.cnblogs.com/DjlNet/p/7572174.html
前言
這里相信大部分玩家之前現在都應該有過使用定時器的時候或者需求,例如什么定時發送郵件通知,定時篩選取消客戶下單未支付的訂單,定時數據備份或者歸檔清洗什么的諸如此類的需求,都是定時的作用的地方,類似比如:windows的計划任務、數據庫的計划任務都是同樣體現,那么相關於這方面的輪子或者發動機就孕育而生了,也有一直一來沒怎么人使用的微軟框架自帶5種Timer系列等等......
額,關於今天要說的這個定時器框架,相信無論是java還是.net開發者都肯定聽說過了,當然這個定時器有些年生了是個老字號了並不表示它就真的老了哦,去github:https://github.com/quartznet/quartznet看了才知道,雖然是移植過來的但是作者是一個人在維護呀,還很積極在commit和解答issues,這里還是佩服一下作者的勇氣和那份責任,順手Star一下加加油,而且最新的3.x也正在開發基於.net標准的做新的nuget package https://www.nuget.org/packages/Quartz/3.0.0-alpha3 開發,這里博主大致使用一下主要有一個亮點是把以前的依賴包都整合了,體現就是一個nuget包就OK了看不見什么Common.Logging之類的了,其中還有些Feature或者Fixes(見3.X文檔),期待穩定正式版吧!!!...
然后關於這個框架的自述或者介紹什么的,園子文章很多啦或者官方文檔https://www.quartz-scheduler.net介紹的都挺清楚的,其中文檔中有12個學習課程(其中發現園友對文檔的中文翻譯,把園中關於此框架的部分文章看了一下,具體想去了解關鍵字搜索即可,不過較多是大致介紹和簡單使用的居多,部分都是自我集成在自己或者公司的系統中去了,形成系統中的一環或者整個任務調度平台覆蓋到系統層面之上,所以看如何設計整合和正確姿勢使用才是關注的重點嘛!!),全部刷一遍也費不了多少時間的,也就是少幾集電視嘛,然后這樣就會對框架設計或者學習使用都基本有個大致了解,后面催牛的時候不至於讓人家以為你在瞎BB吶,哈哈。那么,今天我們寫文是為了記錄什么吶?額,主要是博主本身在學習過程的遇到一些不解或者需要划重點的東西記錄一下吧,233333......
Quartz.Net框架設計概覽
這里有挺多的關鍵詞構成了整個關系鏈的組織,俗話說得好,一張圖勝過幾百上千字,所以這里博主靠着拙劣的構想腦回路簡述一下框架帶來的大體設計思想。
額,這張圖也花了一兩小時完成責在歸納了關於此框架整體結構和組織構成,從中一些備注和標識可以看出,Quarzt.Net再設計上面還是中規中矩,能考慮到的都盡可能考慮了,例如什么具體執行任務與觸發器以及調度器三者分離,實現了組件職責單一原則同時吶又可以復用組件一舉兩得,這樣一來開發者就就可以按組件開發自由組合,再者就是運用了些許設計模式以及面向接口抽象編程的思想,設計模式上面的功效就是讓代碼實現了抽象和細節剝離,也就是封裝隔離了變化點,讓后面的需求或者變化盡可能得可以掌控以助於對整體結構的造成沖擊力降到最低,然后基於面向接口編程也使得后期開發者自主實現可替換性的組件替換時有了機會,所以從這個層面看一個框架或者一個組件,就可以大致知道作者當初在設計的時候,為什么要這樣,以至於這樣之后帶來的好處,什么擴展性、易用性、靈活性等等也是從中體現的吧....那么在學習了人家的設計思想之后,再看看自己系統的代碼可謂是有心無力呀,當然拉老項目固然是如此,不過呢對一個人思想上面的影響效果遠大於一行代碼來的更加長遠,所以面對新項目的時候就可得好好構想一下,運用一下所謂的平衡術( 只有合適的框架沒有最好的框架 )....
Quartz.Net文檔划重點與驗證
這里着重記錄一下文檔中個人覺得比較關鍵之處,此外會用代碼的方式去驗證,做到斤斤計較,哈哈,由於2.X與3.X差距還是有的,但是3.X還沒正式版,所以我們的系統中集成的還是2.X,那么我們還是針對2.X文檔來做學習哈,不過3.X文檔博主已經擼了一遍了差距甚小,以及使用方式基本相似的,具體看版本遷移中的介紹也行...
Job注意事項
這里在官方文檔Job一章中也有詳細介紹,但是博主這里用自己的話理解一下,當然英文還可以的童鞋可以去看文檔,博主還要翻譯插件才能湊合閱讀吶...
1、關於IJob與IJobDetail注意點,實現接口IJob的類的實例只是代表了要具體執行的任務邏輯而已,而實現了IJobDetail的類的實例才是包含此任務的細節,通過JobBuilder.Create創建,但是這里的創建也正如上圖所訴不是“真正的創建”而是傳遞了JobType在JobDetial中保存引用而已,而后面具體在scheduler.JobFactory中來接管IJob具體實例的創建工作,這樣一來你的ioc容器就可以在自定義的XXXJobFactory中去自定義NewJob的創建過程,注入你的XXXService或者XXXRepository,當然也可以直接使用ServiceLoader.Resolve服務定位器模式在Execute中直接解析拿出來使用也可以,但是注意一點Quartz.Net是每次執行Job的時候都是會新創建一個Job實例的,所以注意ioc容器注入對象的生命周期的合理性。 大致去nuget上面搜索一下(某些包最近commit時間略顯久遠了),autofac、unity、Ninject都提供了第三方集成Quarzt.Net包,大致看了一下代碼量很少只有幾個類而已,編寫套路大體相近某些還接管了ISchedulerFactory創建等,所以自己需要為Quarzt集成第三方ioc的時候,可以考慮借鑒代碼自己實現。
2、JobDetail關鍵屬性與Job.Execute異常處理:
(1)當一個jobDetail的job運行執行的時間大於它的trigger觸發器的間隔調度時間的時候,就會發生它的上次任務還沒運行完,接着又開始了下一個任務,或者多個trigger同時觸發執行同一個jobDetail的任務的時候都會造成這個jobDetail的job任務並發執行,通過在YourJob:IJob
YourJob
打上[DisallowConcurrentExecution]
,其實就是一個簡單的屬性類標記一下這個類而已,然后作用於具體對應的JobDetial實例(這句話很關鍵),然后JobDetail的Job執行邏輯在上述兩種情況下都可以按照理想執行了,以上博主已經通過代碼驗證過了哈,注意:多個trigger綁定同一個jobdetail需要jobDetail->StoreDurably() +trigger-> ForJob() **
(2)YourJob標記: [PersistJobDataAfterExecution] 可以記住jobDetail.JobDataMap的值,所以你可以在job的執行中修改它的值,在下一次執行時候可以拿到更新之后的值拉,所以在這種情況下需要記住上次狀態,當然就需要 [DisallowConcurrentExecution] 來做支持拉,這個自然是可以理解的
(3)Durability** 持久的存儲作用於jobdetail,RequestsRecovery 請求恢復作用於jobdetail當出現崩潰類似場景時使用, JobExecutionException 當job執行時需要使用try-catch來截獲所有異常,且再次向上次拋出異常需要包裝成Quartz認識的異常類型JobExecutionException ,且可以設置JobExecutionException 的可用屬性,當然你可以使用BaseJob之類的或者AOP(castle dynamic proxy)的方式來實現job執行當中的日志、異常處理等
Trigger其他說明
Trigger:上圖基本介紹的差不多了,注意這里有個RepeatForever( 與RequestRecovery不要搞混了咯,2333樓主都看花眼了一不小心... )、以及RepeatCount可能是你挺常用的屬性哦,這里補充一下Priority優先級默認是5,當出現資源爭搶的時候例如:線程不夠,會按照優先級來分配資源執行,以及每種Trigger有自己對應的熄火指令 Misfire Instructions,就是在調度器關閉或者應用程序結束的時候或者調度線程資源不夠的時候會發現Trigger暫時性的熄火,默認情況下直接使用智能策略就行了
JobStores 持久化與 Clustering 集群
這里官方對JobStores提供了兩種模式,https://www.quartz-scheduler.net/documentation/quartz-2.x/tutorial/job-stores.html,其中一般情況還是推薦使用 RAMJobStore,因為不論是存儲拿數據還是CPU級別的調度都是最佳的,但是缺點也是比較明顯的就是對需要持久化的信息在應用程序重啟之后就丟失了,例如XXJob的上次激發時間最后執行時間等,所以框架自身提供了AdoJobStore 可以找到對應的 DB Provider配置 即可,然后 Clustering 集群肯定是基於上面的 AdoJobStore 數據庫存儲模式下的,可以解決負載均衡和故障轉移的功能。
這里博主將會做實驗去驗證,同時在園子中搜索發現已經有同學去實踐了,這里引用一下各位大大的博文地址(應該不會介意,嘻嘻),大家也可以參考學習:
http://www.cnblogs.com/knowledgesea/p/5145239.html Quartz.net持久化與集群部署開發詳解
http://www.cnblogs.com/mushroom/p/4231642.html#3760225 Net作業調度(四)—quartz.net持久化和集群
http://www.cnblogs.com/lanxiaoke/p/6629481.html 任務調度之持久化(基於Quartz.net)
http://www.cnblogs.com/lanxiaoke/p/6637714.html 任務調度之集群(基於Quartz.net)
http://www.cnblogs.com/huangxincheng/p/6916246.html 使用sqlserver搭建高可用雙機熱備的Quartz集群部署【附源碼】
這里博主這里就不在啰嗦怎么操作了(上文有仔細操作),直接去做實驗驗證即可,實驗之前先說幾個注意事項:
1、在持久化模式下面,當Scheduler調度器總是執行和線程池相當的任務job數的時候,數據庫連接數盡量要保證是 ThreadPool線程數量+1 的狀態,前提是沒超過最大連接數
2、在集群的情況下,當jobDetail設置為 RequestsRecovery -> true 當前的JobDetail才有效果
3、集群配置:除了線程池數量,instanceId可以不同外,各個節點的配置必須是一樣的
4、集群中節點的系統時間一致
5、注意持久化和集群模式下的配置項
在經過一番測試集群發現,Quartz.Net會自動沖裁不可用的節點,讓一個可用節點來執行,內部有機制去測試每個節點的可用情況會定時去檢測然后剔除,以及新晉節點的加入等,也正好體現了所謂的負載均衡和故障轉移咯,博主測試環境:應用程序Console、數據庫Sql server localdb,本機附帶兩個應用實例測試,發現切換節點時間差為16s...
Quartz.Net UI控制台
**CrystalQuartz : github地址:https://github.com/guryanovev/CrystalQuartz **至於集成方式、方法項目地址當中有說明拉...
總結
哇,不知不覺居然還花了挺多的時間去學習與研究這個框架,先是了解生態圈中的定時器框架比較流行的(當然也包含了Hangfire后面我們有機會也去學習學習),然后想着之前對Quartz有點懵懵懂懂的,所以就去系統的看了看官方文檔了解了解人家的設計思想和實現,到了后面帶着問題去看了些源代碼哈,然后跟着官方的文檔基本擼了一遍之后,包含了去園中也去閱讀了其他博主的相關博客,以及周邊衍生物的 CrystalQuartz UI控制台界面等等。
最后吶,各位看官老爺覺得還可以的話,您的評論和點贊都是對博主的肯定支持或者斧正!!!哈哈,博主會接着繼續框架學習與研究系列....
更新(2017年9月26日00:36:19)
關於評論同學的問題,做了相應的回答和測試,本着我們打破砂鍋問到底的精神,我們繼續來測試研究一下IScheduler的API方法,其實大致看了一下API文檔(2.x):http://quartznet.sourceforge.net/apidoc/2.0/html/html/edbcd9ad-0bf8-2b0e-52c9-e8a62ac4f610.htm 直接關鍵詞搜索: IScheduler 即可,然后選擇Method就可以看到很多API了找個還是比較好的且文檔的注釋其實就是最好的解釋和說明了哈,就不用去github找了哈,這個文檔也是自動生成的哦,多一句廢話....然后什么暫停刪除重啟jobdetail、trigger都有說明的,這里博主總結一下就是:核心對象是jobdetail對象實例,trigger都是圍繞它的,其次才是scheduler,然后jobdetail實質邏輯是IJob實例承載,所以這樣一來對jobdetail的刪除暫停都會影響到對應的trigger,但是一個jobdetail被多個trigger觸發時,某個trigger暫停對jobdetail來說是透明不知道的,這點從一開始的組件分離設計也體現出來了,所以搞清楚了它們的關系也就自然明白了調度是誰與誰之間的相互關系、相互制約了哈,時間不早了,各位晚安!!!
后續補充(2017年9月27日00:34:17)
1、關於使用IScheduler的API:PauseJob 暫停任務之后,下次再重啟 ResumeJob 的時候,中途的間隔時間差,將會由該 JobDetial 所從屬的 Triggers 去補償,前提是這些 Triggers 還有執行的機會,例如 RepeatForever計算自身的Interval時間間隔與JobDetail中斷間隔的時間倍數自覺補償丟失執行的次數 或者 RepeatCount 還有剩余次數的情況,將會消耗剩余次數去彌補JobDetail中斷的時間差等(注意補償的時刻:是JobDetail一旦重啟的那個時刻);同理還存在 PauseTrigger 暫定某個觸發器之后,下次再啟動 ResumeTrigger 該觸發器的時候,中間間隔的時間差,如果該 Trigger 還有機會執行,將會去彌補丟失的次數,同樣是舉例:RepeatForever通過自身的Interval時間間隔與時間差比較然后執行需要彌補的次數 或者 RepeatCount 還有剩余次數的情況,將會消耗剩余次數去彌補時間差(注意補償的時刻:是該Trigger一旦重啟的那個時刻)。以上所述吶,博主相繼實驗上述的情況情況屬實,這個應該框架本身一種約定或者意識補償機制,並且有一位園友也發現了提醒了博主,謝過......再次補充樓主再次想到 [DisallowConcurrentExecution] 可能會有影響,再次把上面的測試再來了一發,結果依然如此,那就是框架已經控制了,測試途中發現在補償的時間點方面,基於 RAMJobStore 情況下,補償激發時刻時間精度十分標准的讓人難以置信,集中激發次數的時刻都幾乎一致,博主這里測試的是 {DateTime.Now:yyyy-MM-dd HH:mm:ss.ffffff}
ffffff 幾乎一致,最后幾位稍微有些差值,根據測試當時情況而定.....23333
畢個例子上述某種情況,測試看下圖(注意紅框部分既是激發時刻與補償次數):
我去,時間又不早了,該休息了,每日一學,千里之行始於足下!!!
更新(2017年10月6日11:29:25)
1、關於使用winservice來作為定時器quartz.net的宿主程序,這里社區提供了Topshlf(https://github.com/Topshelf/Topshelf)以及對應quartz.net的擴展庫Topshelf.Integrations(https://github.com/dtinteractive/Topshelf.Integrations/),至於用不用以及用不用得上那就看自己了,只是說這有些解決方案而已,再者TopShlf確實方便靈活包括部署編寫和使用,讀者可自行去wiki跟着看看大致有個了解,做到心中有數,使用winservice部署還可以一定程序上的解決了重啟的問題,就不會像iis一樣有回收需要重啟定時器的問題,也算是定期器不較好的宿主方式了吧
2、那么在不同宿主的情況下,我們想通過UI界面觀察和了解當前調度器的任務和觸發器的情況,以及本身的大致情況有一個了解,該怎么辦嗯,這里社區是萬能的,哈哈我們只是代碼的搬運工和使用方,提供了CrystalQuartz(具體看詳情鏈接:https://github.com/guryanovev/CrystalQuartz)鏈接內容簡直不能太詳細了,具體可以看demo:https://github.com/guryanovev/CrystalQuartz/tree/master/examples 介紹了各種情況下面的解決方案,大體有下面幾種方式:CrystalQuartz.Simple、CrystalQuartz.Remote、CrystalQuartz.Owin 三種方式,相信在實際運用過程中,應該有你想要使用的場景吧
關於Quartz.Net本身及其周邊就說到這里,期間也更新了幾次,哈哈,由於后面去了解周邊才有所了解去看來wiki和一些教程,由此在這里再次記錄一下以此備份或者加深印象。好的,接下來將會對一個框架及其周邊衍生物做出如同的學習和探究.......
二度更新(2017年10月17日20:03:15)
Quartz.Net 作者已經更新了支持.net standrad2.0了,就意味着可以在.net core平台使用了,雖然發布的beta版本但是下載數量已經有了好幾百,下載地址:https://www.nuget.org/packages/Quartz/3.0.0-beta1,更新說明地址:https://www.quartz-scheduler.net/2017/10/08/quartznet-3.0-beta1-released.html,相信不久之后就會發布release版本,屆時.net core框架開發就不怕沒有定時器的支持了,~( ̄▽ ̄~)(~ ̄▽ ̄)~