沈宇杰
在現代的企事業單位,越來越多地涉及到作業調度系統.例如金融、保險、網站和計算機管理系統等主流的工作場景,都需要合理的調度,對于訪問量和工作量巨大的電商類任務場景,利用Quartz框架可以很好地幫助企業實現常規和復雜的作業調度功能[1].Quartz是開源組織“Open-Symphony”的一個項目[2],它完全基于Java實現,具有強大的調度功能[3].本文以Quartz為核心,提出了一種改進型系統.與基本的Quartz系統相比,改進型系統增添了“精確調度”“自定義任務”等新功能,增強了Quartz的功能.
關于作業調度,有些開發者在遇到此類問題時,一般會考慮使用Web Services實現[4].而本系統則利用Quartz的基本框架,添加了新的功能邏輯.為確保可伸縮性,采用了基于多線程的架構[5].本系統主要分為調度引擎和自定義任務引擎兩個部分.
①調度引擎,主要負責任務的精確設定與觸發.
②自定義任務引擎,主要負責兩項工作,即標準化自定義任務接口和任務執行系統初始化時,調度引擎服務啟動Quartz服務,分別在計算機內存和數據庫中注冊調度信息.當一個調度信息被Quartz服務觸發后,其包含的任務信息被寫入數據庫.隨后,自定義任務引擎則輪詢數據庫中的這些任務信息,將符合條件的任務放入線程池中運行.
改進型系統與基本Quartz系統比較的差異見圖1.

圖1 本系統與基本Quartz系統的比較
本系統的數據庫主要使用的表如下.
①Schedule表:存放作業調度信息,Quartz基本框架將使用該表.
②Configuration表:存放配置信息,調度引擎將使用該表.
③Job表:存放要進入線程池的自定義任務,自定義任務引擎將使用該表.
④TimeTrigger表:作業調度的觸發信息,Quartz基本框架將使用該表.
⑤JobDefinition表:自定義作業的具體細節,自定義任務引擎將使用該表.
⑥Joblog表:存放作業日志信息,自定義任務引擎將使用該表.
上述各數據表中,Configuration表具有獨立性,其他各數據表之間的邏輯關系如圖2所示.

圖2 系統的數據表邏輯關系
本系統要實現自定義任務的運行,必須為任務設定通用的接口模式,當添加自定義任務時,主要有以下6個接口方法需要實現.
①execute():該方法執行任務的具體業務邏輯,是核心方法.當自定義任務的實例任務引擎執行時,該方法被調用.
②pause():暫停任務的運行.如果暫停成功,結果返回true,結果失敗返回false.
③resume():恢復任務的運行.它與pause方法相反,先暫停后才能恢復.恢復成功返回結果true,如果失敗則返回結果false.
④stop():終止任務的運行,成功則返回true,失敗則返回false.若該作業不支持停止操作,則直接返回false.
⑤onQueue():任務在進入執行線程池前,需要調用的方法.用于執行前的準備工作.
⑥onComplete():任務執行完畢后的結束動作,在execute方法執行完畢后被調用.比如任務完成后,需要將生成的結果,通過E-mail發給相關的人員.發E-mail這個動作就可以在onComplete方法中實現.
當系統導入Quartz插件包后,需要繼承Quartz的接口,才能使用其功能.在Quartz中,有以下幾個基本的類.
(1)Schdeuler類:這個類的方法將調度信息寫入系統內存,使Quartz在設定的時間觸發調度.
(2)Job接口:用戶自定義任務的統一接口.開發人員將自定義邏輯寫入Job接口的execute方法中,一旦任務觸發,Job類的execute方法將會被調用,執行寫入的自定義邏輯.
(3)JobExecuteContext類:此類是 Scheudler類與Job類通訊的上下文參數.
(4)JobDetail類:自定義任務的具體參數,被Schedule.scheduleJob(JobDetail,Trigger)方 法 所使用.
(5)Trigger類:此類是Quartz的核心類,用于設置開始時間、結束時間、執行次數等等,并可以設置兩種觸發類型,分別是Corn表達式型和Intval型.
①Cron表達式方式.Cron表達式是Quartz特有的字符串,當Quartz讀取到該類字符串時,會自動解析出該表達式的含義,從而實現用戶期望的調度信息,表達式分為七段,分別表示秒,分,時,日,月,周,年.示例見表1.

表1 具體示例
②Inteval方式.Inteval方式與Cron表達式不同,它以精確的間隔來觸發任務,即每次觸發的間隔是恒定不變的.Quartz中間隔的單位一般是秒,分,時,最大不會大于小時.
在Quartz的架構中,新任務必須先實現Job類的接口,將業務邏輯代碼寫入execute方法,并設置任務的具體參數(JobDetail類)和調度信息(Trigger類).隨后系統調用Schdeule類的Schedule.scheduleJob(JobDetail,Trigger)方法,將新任務寫入系統進程.當調度信息被觸發時,任務引擎通過Schdeule類解析出jobDetail類中包含的作業參數,傳遞給新任務實例,用于execute方法的執行.
這個模塊主要實現2部分功能,一是將任務信息注冊到Quartz實時服務中;二是實現已注冊任務信息的持久化.
(1)持久化.計算機一旦出現重啟或者服務中斷,必須將Quartz實時服務中的任務信息寫入數據庫,否則系統因為各種原因重啟時,就會失去所有任務的狀態,導致調度的混亂.持久化的構建方法是使用Schedule表和Trigger表存儲現場信息.Schedule表是用于存儲調度信息的,包含自定義任務的類型、調度的類型、調度的狀態、觸發器的信息、任務執行信息等等.trigger表存儲每個調度信息的具體調度計劃,例如觸發時間、已觸發次數、剩余觸發次數等等.
(2)新的調度信息創建時,系統會將任務信息、觸發時間、觸發次數等具體參數進行封裝,傳遞給調度引擎.調度引擎根據封裝內容,把相應的調度信息插入Schedule表和Trigger表,并通過ID關聯兩者.這樣一個任務的調度信息就被寫入數據庫,完成了持久化的第一步.第二步,調度引擎將調度信息注冊到Quartz實時服務,Quartz實時服務就開始掌管這條新的調度信息了.
核心代碼如下:

當觸發時刻到來時,Quartz實時服務將啟動一個調度,讀取任務的類型,將自定義任務插入Job表中,供隨后的自定義任務引擎輪詢篩選.插入操作完成后,修改Schedule表與Trigger表中的觸發狀態信息,使之與內存中調度信息一致(見圖3).

圖3 調度信息的設置
在這期間,如果發生服務器重啟,Quartz實時服務中的調度信息都會丟失.所以在服務器重啟時,調度引擎會根據Schedule表與Trigger表還原現場,將所有未完成的調度信息重新插入內存,這樣調度系統可以繼續工作了.
為了避免服務重啟時,大量丟失的任務同時觸發,導致資源耗盡,調度引擎服務需要設置自己的規則,當同一個調度丟失多個周期的調度觸發,在重啟還原時只觸發一次(見圖4).

圖4 系統重啟時還原現場
(3)線程池將這條新的作業加入線程隊列.
(4)作業開始被線程池執行.
(5)作業運行完畢,自定義任務引擎修改作業的狀態,最終成為“完成”或者“失敗”.

圖5 自定義任務引擎模塊的工作流程
本模塊使用了多線程技術,以此來實現并發處理任務.該技術主要解決多個線程同時需要執行的問題,顯著減少處理環節的閑置時間,增加處理器吞吐的能力,很好地實現多任務并發.在本系統中,自定義任務引擎是執行任務的模塊,所以主要使用線程池技術來實現并發執行.本系統線程池的實現是通過使用Java提供的java.util.concurrent工具包來實現的.
調度引擎啟動一個任務時,將任務信息插入數據庫中的Job表,而自定義任務引擎每4秒輪詢這張Job表.當Job表中的任務信息符合條件時,就會進入線程池按照順序執行.
任務運行時的關鍵點,是提供自定義作業的運行參數,自定義任務引擎會讀取任務記錄里的Schedule Id字段去關聯Schedule表中相應調度信息,讀取記錄中的ParamterContext內容,作為任務運行的參數.運行的流程見圖5.
(1)調度引擎模塊將符合調度觸發的自定義任務寫入Job表.
(2)自定義任務引擎每4秒輪詢這張Job表,當有新記錄加入時,這條記錄被放入線程池.
本系統在Quartz基本框架的基礎上,設計并實現了一個可自定義作業的調度系統,該系統能夠應用于大多數的作業調度場合,采用了并發技術和線程池,能夠有效應對系統的掉電與重啟,并能夠根據用戶的需要,添加自定義的任務作業.本系統的設計思路可以應用于其他作業調度的應用場景,具有一定的參考價值.