沈宇杰
在現(xiàn)代的企事業(yè)單位,越來越多地涉及到作業(yè)調(diào)度系統(tǒng).例如金融、保險、網(wǎng)站和計算機管理系統(tǒng)等主流的工作場景,都需要合理的調(diào)度,對于訪問量和工作量巨大的電商類任務(wù)場景,利用Quartz框架可以很好地幫助企業(yè)實現(xiàn)常規(guī)和復(fù)雜的作業(yè)調(diào)度功能[1].Quartz是開源組織“Open-Symphony”的一個項目[2],它完全基于Java實現(xiàn),具有強大的調(diào)度功能[3].本文以Quartz為核心,提出了一種改進型系統(tǒng).與基本的Quartz系統(tǒng)相比,改進型系統(tǒng)增添了“精確調(diào)度”“自定義任務(wù)”等新功能,增強了Quartz的功能.
關(guān)于作業(yè)調(diào)度,有些開發(fā)者在遇到此類問題時,一般會考慮使用Web Services實現(xiàn)[4].而本系統(tǒng)則利用Quartz的基本框架,添加了新的功能邏輯.為確??缮炜s性,采用了基于多線程的架構(gòu)[5].本系統(tǒng)主要分為調(diào)度引擎和自定義任務(wù)引擎兩個部分.
①調(diào)度引擎,主要負責任務(wù)的精確設(shè)定與觸發(fā).
②自定義任務(wù)引擎,主要負責兩項工作,即標準化自定義任務(wù)接口和任務(wù)執(zhí)行系統(tǒng)初始化時,調(diào)度引擎服務(wù)啟動Quartz服務(wù),分別在計算機內(nèi)存和數(shù)據(jù)庫中注冊調(diào)度信息.當一個調(diào)度信息被Quartz服務(wù)觸發(fā)后,其包含的任務(wù)信息被寫入數(shù)據(jù)庫.隨后,自定義任務(wù)引擎則輪詢數(shù)據(jù)庫中的這些任務(wù)信息,將符合條件的任務(wù)放入線程池中運行.
改進型系統(tǒng)與基本Quartz系統(tǒng)比較的差異見圖1.
圖1 本系統(tǒng)與基本Quartz系統(tǒng)的比較
本系統(tǒng)的數(shù)據(jù)庫主要使用的表如下.
①Schedule表:存放作業(yè)調(diào)度信息,Quartz基本框架將使用該表.
②Configuration表:存放配置信息,調(diào)度引擎將使用該表.
③Job表:存放要進入線程池的自定義任務(wù),自定義任務(wù)引擎將使用該表.
④TimeTrigger表:作業(yè)調(diào)度的觸發(fā)信息,Quartz基本框架將使用該表.
⑤JobDefinition表:自定義作業(yè)的具體細節(jié),自定義任務(wù)引擎將使用該表.
⑥Joblog表:存放作業(yè)日志信息,自定義任務(wù)引擎將使用該表.
上述各數(shù)據(jù)表中,Configuration表具有獨立性,其他各數(shù)據(jù)表之間的邏輯關(guān)系如圖2所示.
圖2 系統(tǒng)的數(shù)據(jù)表邏輯關(guān)系
本系統(tǒng)要實現(xiàn)自定義任務(wù)的運行,必須為任務(wù)設(shè)定通用的接口模式,當添加自定義任務(wù)時,主要有以下6個接口方法需要實現(xiàn).
①execute():該方法執(zhí)行任務(wù)的具體業(yè)務(wù)邏輯,是核心方法.當自定義任務(wù)的實例任務(wù)引擎執(zhí)行時,該方法被調(diào)用.
②pause():暫停任務(wù)的運行.如果暫停成功,結(jié)果返回true,結(jié)果失敗返回false.
③resume():恢復(fù)任務(wù)的運行.它與pause方法相反,先暫停后才能恢復(fù).恢復(fù)成功返回結(jié)果true,如果失敗則返回結(jié)果false.
④stop():終止任務(wù)的運行,成功則返回true,失敗則返回false.若該作業(yè)不支持停止操作,則直接返回false.
⑤onQueue():任務(wù)在進入執(zhí)行線程池前,需要調(diào)用的方法.用于執(zhí)行前的準備工作.
⑥onComplete():任務(wù)執(zhí)行完畢后的結(jié)束動作,在execute方法執(zhí)行完畢后被調(diào)用.比如任務(wù)完成后,需要將生成的結(jié)果,通過E-mail發(fā)給相關(guān)的人員.發(fā)E-mail這個動作就可以在onComplete方法中實現(xiàn).
當系統(tǒng)導(dǎo)入Quartz插件包后,需要繼承Quartz的接口,才能使用其功能.在Quartz中,有以下幾個基本的類.
(1)Schdeuler類:這個類的方法將調(diào)度信息寫入系統(tǒng)內(nèi)存,使Quartz在設(shè)定的時間觸發(fā)調(diào)度.
(2)Job接口:用戶自定義任務(wù)的統(tǒng)一接口.開發(fā)人員將自定義邏輯寫入Job接口的execute方法中,一旦任務(wù)觸發(fā),Job類的execute方法將會被調(diào)用,執(zhí)行寫入的自定義邏輯.
(3)JobExecuteContext類:此類是 Scheudler類與Job類通訊的上下文參數(shù).
(4)JobDetail類:自定義任務(wù)的具體參數(shù),被Schedule.scheduleJob(JobDetail,Trigger)方 法 所使用.
(5)Trigger類:此類是Quartz的核心類,用于設(shè)置開始時間、結(jié)束時間、執(zhí)行次數(shù)等等,并可以設(shè)置兩種觸發(fā)類型,分別是Corn表達式型和Intval型.
①Cron表達式方式.Cron表達式是Quartz特有的字符串,當Quartz讀取到該類字符串時,會自動解析出該表達式的含義,從而實現(xiàn)用戶期望的調(diào)度信息,表達式分為七段,分別表示秒,分,時,日,月,周,年.示例見表1.
表1 具體示例
②Inteval方式.Inteval方式與Cron表達式不同,它以精確的間隔來觸發(fā)任務(wù),即每次觸發(fā)的間隔是恒定不變的.Quartz中間隔的單位一般是秒,分,時,最大不會大于小時.
在Quartz的架構(gòu)中,新任務(wù)必須先實現(xiàn)Job類的接口,將業(yè)務(wù)邏輯代碼寫入execute方法,并設(shè)置任務(wù)的具體參數(shù)(JobDetail類)和調(diào)度信息(Trigger類).隨后系統(tǒng)調(diào)用Schdeule類的Schedule.scheduleJob(JobDetail,Trigger)方法,將新任務(wù)寫入系統(tǒng)進程.當調(diào)度信息被觸發(fā)時,任務(wù)引擎通過Schdeule類解析出jobDetail類中包含的作業(yè)參數(shù),傳遞給新任務(wù)實例,用于execute方法的執(zhí)行.
這個模塊主要實現(xiàn)2部分功能,一是將任務(wù)信息注冊到Quartz實時服務(wù)中;二是實現(xiàn)已注冊任務(wù)信息的持久化.
(1)持久化.計算機一旦出現(xiàn)重啟或者服務(wù)中斷,必須將Quartz實時服務(wù)中的任務(wù)信息寫入數(shù)據(jù)庫,否則系統(tǒng)因為各種原因重啟時,就會失去所有任務(wù)的狀態(tài),導(dǎo)致調(diào)度的混亂.持久化的構(gòu)建方法是使用Schedule表和Trigger表存儲現(xiàn)場信息.Schedule表是用于存儲調(diào)度信息的,包含自定義任務(wù)的類型、調(diào)度的類型、調(diào)度的狀態(tài)、觸發(fā)器的信息、任務(wù)執(zhí)行信息等等.trigger表存儲每個調(diào)度信息的具體調(diào)度計劃,例如觸發(fā)時間、已觸發(fā)次數(shù)、剩余觸發(fā)次數(shù)等等.
(2)新的調(diào)度信息創(chuàng)建時,系統(tǒng)會將任務(wù)信息、觸發(fā)時間、觸發(fā)次數(shù)等具體參數(shù)進行封裝,傳遞給調(diào)度引擎.調(diào)度引擎根據(jù)封裝內(nèi)容,把相應(yīng)的調(diào)度信息插入Schedule表和Trigger表,并通過ID關(guān)聯(lián)兩者.這樣一個任務(wù)的調(diào)度信息就被寫入數(shù)據(jù)庫,完成了持久化的第一步.第二步,調(diào)度引擎將調(diào)度信息注冊到Quartz實時服務(wù),Quartz實時服務(wù)就開始掌管這條新的調(diào)度信息了.
核心代碼如下:
當觸發(fā)時刻到來時,Quartz實時服務(wù)將啟動一個調(diào)度,讀取任務(wù)的類型,將自定義任務(wù)插入Job表中,供隨后的自定義任務(wù)引擎輪詢篩選.插入操作完成后,修改Schedule表與Trigger表中的觸發(fā)狀態(tài)信息,使之與內(nèi)存中調(diào)度信息一致(見圖3).
圖3 調(diào)度信息的設(shè)置
在這期間,如果發(fā)生服務(wù)器重啟,Quartz實時服務(wù)中的調(diào)度信息都會丟失.所以在服務(wù)器重啟時,調(diào)度引擎會根據(jù)Schedule表與Trigger表還原現(xiàn)場,將所有未完成的調(diào)度信息重新插入內(nèi)存,這樣調(diào)度系統(tǒng)可以繼續(xù)工作了.
為了避免服務(wù)重啟時,大量丟失的任務(wù)同時觸發(fā),導(dǎo)致資源耗盡,調(diào)度引擎服務(wù)需要設(shè)置自己的規(guī)則,當同一個調(diào)度丟失多個周期的調(diào)度觸發(fā),在重啟還原時只觸發(fā)一次(見圖4).
圖4 系統(tǒng)重啟時還原現(xiàn)場
(3)線程池將這條新的作業(yè)加入線程隊列.
(4)作業(yè)開始被線程池執(zhí)行.
(5)作業(yè)運行完畢,自定義任務(wù)引擎修改作業(yè)的狀態(tài),最終成為“完成”或者“失敗”.
圖5 自定義任務(wù)引擎模塊的工作流程
本模塊使用了多線程技術(shù),以此來實現(xiàn)并發(fā)處理任務(wù).該技術(shù)主要解決多個線程同時需要執(zhí)行的問題,顯著減少處理環(huán)節(jié)的閑置時間,增加處理器吞吐的能力,很好地實現(xiàn)多任務(wù)并發(fā).在本系統(tǒng)中,自定義任務(wù)引擎是執(zhí)行任務(wù)的模塊,所以主要使用線程池技術(shù)來實現(xiàn)并發(fā)執(zhí)行.本系統(tǒng)線程池的實現(xiàn)是通過使用Java提供的java.util.concurrent工具包來實現(xiàn)的.
調(diào)度引擎啟動一個任務(wù)時,將任務(wù)信息插入數(shù)據(jù)庫中的Job表,而自定義任務(wù)引擎每4秒輪詢這張Job表.當Job表中的任務(wù)信息符合條件時,就會進入線程池按照順序執(zhí)行.
任務(wù)運行時的關(guān)鍵點,是提供自定義作業(yè)的運行參數(shù),自定義任務(wù)引擎會讀取任務(wù)記錄里的Schedule Id字段去關(guān)聯(lián)Schedule表中相應(yīng)調(diào)度信息,讀取記錄中的ParamterContext內(nèi)容,作為任務(wù)運行的參數(shù).運行的流程見圖5.
(1)調(diào)度引擎模塊將符合調(diào)度觸發(fā)的自定義任務(wù)寫入Job表.
(2)自定義任務(wù)引擎每4秒輪詢這張Job表,當有新記錄加入時,這條記錄被放入線程池.
本系統(tǒng)在Quartz基本框架的基礎(chǔ)上,設(shè)計并實現(xiàn)了一個可自定義作業(yè)的調(diào)度系統(tǒng),該系統(tǒng)能夠應(yīng)用于大多數(shù)的作業(yè)調(diào)度場合,采用了并發(fā)技術(shù)和線程池,能夠有效應(yīng)對系統(tǒng)的掉電與重啟,并能夠根據(jù)用戶的需要,添加自定義的任務(wù)作業(yè).本系統(tǒng)的設(shè)計思路可以應(yīng)用于其他作業(yè)調(diào)度的應(yīng)用場景,具有一定的參考價值.