《電子技術應用》
您所在的位置:首頁 > 通信與網(wǎng)絡 > 設計應用 > Web應用異步任務處理的實現(xiàn)研究
Web應用異步任務處理的實現(xiàn)研究
來源:微型機與應用2012年第4期
張文梅, 廖福保
(廣東農(nóng)工商職業(yè)技術學院 機電系, 廣東 廣州 510507)
摘要: 簡述了異步任務處理在復雜Web應用中的必要性,利用Java中基于線程池的執(zhí)行框架,分析并設計了相應的任務調(diào)度方法,以解決Web應用中大型任務處理時間長與系統(tǒng)要求響應時問短的矛盾,實現(xiàn)了用于處理復雜任務的異步調(diào)度,提高系統(tǒng)的可靠性。
Abstract:
Key words :

摘  要: 簡述了異步任務處理在復雜Web應用中的必要性,利用Java中基于線程池的執(zhí)行框架,分析并設計了相應的任務調(diào)度方法,以解決Web應用中大型任務處理時間長與系統(tǒng)要求響應時問短的矛盾,實現(xiàn)了用于處理復雜任務的異步調(diào)度,提高系統(tǒng)的可靠性。
關鍵詞: 異步任務;線程池;異步調(diào)度

    在Web應用中,某些功能的實現(xiàn)邏輯很復雜、執(zhí)行比較耗時[1],例如涉及外部系統(tǒng)調(diào)用、多數(shù)據(jù)源等;此時,希望可以讓這些復雜的業(yè)務邏輯放在后臺執(zhí)行,而前臺與用戶的交互可以不用等待,從而提高用戶體驗;或者需要以一定時間間隔重復運行任務、或在每天的指定時間運行任務的情況。為此,需要控制大型任務對服務器資源的消耗,降低Web服務器的并發(fā)連接數(shù)目,這就需要將大型任務的提交和執(zhí)行分開,使服務器接受任務后立即斷開與客戶端的連接,減少服務器的并發(fā)連接數(shù),而任務則推遲到服務器資源許可時執(zhí)行,以抑制服務器資源的峰值消耗。
    為盡量減少耗時操作對執(zhí)行的影響,本文提出了異步任務的處理,使用多線程來管理耗時任務,作為后臺進程執(zhí)行;同時把任務信息都持久化在數(shù)據(jù)庫中,保證了異步任務處理的靈活性、可靠性。
1 多線程
1.1線程池

    一個線程是程序中的一條執(zhí)行流,是操作系統(tǒng)分配處理器的基本單位。并發(fā)是程序中多條執(zhí)行流的同時推進,多任務并發(fā)對應多線程并發(fā)[2]。
     但是為每個任務創(chuàng)建一個線程,當任務完成時撤消對應的線程存在明顯的缺陷。線程的創(chuàng)建需要一定的時間,給任務請求的響應帶來延遲,線程的創(chuàng)建和撤消也給操作系統(tǒng)帶來額外的管理負擔,若頻繁“創(chuàng)建和撤消”,則將明顯增加系統(tǒng)的額外開銷。為有效降低線程重復創(chuàng)建和撤銷方面的開支可以采用線程池技術。
      線程池技術提供了一種較好的解決方案[3]:系統(tǒng)維護由若干個線程組成的線程池。當有任務請求到達時,由池中的一個線程為之運行,在任務完成后不是將該線程撤消而是將其歸還線程池,使之能夠為后續(xù)到達的任務服務;若線程池中沒有空閑的線程,則任務進入等待狀態(tài)直到有空閑的線程。
1.2 Java中的線程池實現(xiàn)機制
      Java在語言級實現(xiàn)了功能豐富的多線程編程機制[4],對線程池的建立和維護提供了強大的支持。特別在JDK1.5及以后的版本中,任務執(zhí)行抽象的首選不再是Thread,而是Executor。Executor雖是一個簡單的接口,但它提供了異步任務執(zhí)行框架并支持多種不同類型的任務執(zhí)行策略,ExecutorService接口和ScheduledExecutorService接口對Executor進行了擴展,添加了管理線程執(zhí)行和調(diào)度線程池的若干方法。通過Executors工具類提供的靜態(tài)工廠方法可以創(chuàng)建符合特定需求的基于線程池執(zhí)行框架。
     newChachedThreadPool()方法用于創(chuàng)建可緩存線程池的執(zhí)行框架。當新的請求任務到達時,執(zhí)行框架將盡可能地重用池中的空閑線程,若此時池中沒有空閑線程,則添加新線程,這個方法對池的大小沒有限制。另一方面,該執(zhí)行框架能夠自動回收空閑時間超過60 s的線程,以合理使用系統(tǒng)資源。對于執(zhí)行大量短異步任務的程序而言,這種方式的線程池通常可提高性能。
     newFixedThreadPool(int nThreads)方法建立的執(zhí)行框架中的線程池具有固定數(shù)量的線程。每提交一個任務它就創(chuàng)建一個線程,直到達到池的限定值nThreads,線程池的長度不再變化,新到達的任務在一個遵循先來先服務(FIFS)規(guī)則的無界隊列中等待執(zhí)行。
    newScheduledThreadPool(int nThreads)方法建立的執(zhí)行框架中的線程池也是定長的,它支持定時的以及周期性的任務的執(zhí)行。
     這些工廠方法返回的Executor 都是ThreadPoolExecutor()類的常用實例,能滿足大部分線程池的應用需求。
2 設計思路
    為保證異步任務處理的靈活性和可靠性,本文設計的思路為:任務持久化+Java線程池+任務調(diào)度。
2.1 任務持久化
   將待處理的任務信息保存在可信任的數(shù)據(jù)庫中,同時要確保當任務處理服務器出問題后這些未執(zhí)行成功、或未開始執(zhí)行的任務不會被丟失。
2.2 任務調(diào)度
     當任務信息都持久化在數(shù)據(jù)庫中之后,需要將這些信息讀取出來執(zhí)行具體的業(yè)務邏輯操作,本文通過ScheduledExecutorService來實現(xiàn)對任務的循環(huán)調(diào)度,例如可采取每隔2 min掃描一次待處理任務列表,若有記錄則提取出來執(zhí)行。
3 具體實現(xiàn)
    異步任務處理中各組成部分在運行過程中的調(diào)用關系如圖1。

    當客戶端訪問服務器時,有耗時操作的任務,則把該任務放入數(shù)據(jù)庫中。服務器每隔一段時間輪詢存放待處理任務的表,若表中有任務,則任務調(diào)度線程池采用多線程機制來執(zhí)行該任務。任務執(zhí)行成功后,刪除待處理任務表中的該任務信息,否則把該任務信息更新到任務失敗表,進行人工干預。
3.1 任務數(shù)據(jù)表
     建兩張表,一張task表,用來存放待處理的任務;一張task_fail表用來存放失敗的任務。兩張表的結(jié)構(gòu)一樣,結(jié)構(gòu)如表1所示。

    task表主要用來保存所有待處理的任務,每條任務信息屬于一種任務類型,由task_handle字段標識,任務類型值為該類型任務的具體實現(xiàn)類名。task_params 字段提供了執(zhí)行該任務需要的所有參數(shù),為字符串,需要在具體任務實現(xiàn)類中解析。handle_time字段提供了任務待執(zhí)行的日期。
    每條任務被執(zhí)行之后根據(jù)執(zhí)行情況進行刪除或者更新操作,任務成功執(zhí)行,就從task表中刪除該記錄。Task_fail表主要用來保存執(zhí)行失敗、需要人工干預的任務記錄,記錄來源于task表。
3.2 任務處理過程
    任務處理的過程如圖2所示。

    (1)當服務器啟動后,根據(jù)調(diào)度策略每隔一段時間調(diào)度一次,而不管上次調(diào)度是否已經(jīng)執(zhí)行完畢;任務輪詢主線程查詢task表,從中取出一定條的數(shù)據(jù)。
    (2)對從task表中查詢出來的每條記錄,將該條記錄的ID放進本地cache中,根據(jù)記錄中task_handle和task_params字段的值獲得處理該任務對應的類及參數(shù)值,在異步線程池中利用反射機制來執(zhí)行任務。
    (3)具體處理類對該任務處理完成之后返回結(jié)果,系統(tǒng)對tasks表中該條記錄進行刪除,同時將cache中的記錄ID清除、避免cache無限膨脹。若任務處理失敗,系統(tǒng)就把該條記錄插入到task_fail表中,以備人工干預。
    (4)當?shù)竭_下次執(zhí)行時間時,再次掃描tasks表,循環(huán)上面的邏輯。不過這次在任務處理之前,要先在本地cache中查詢是否該條記錄正在被處理,若cache中已經(jīng)存在該條記錄就無需處理了,以避免一些任務被重復并發(fā)執(zhí)行。
3.3 任務輪詢主線程的實現(xiàn)
    Executor的靜態(tài)方法生成一個固定的線程池。線程池的線程是不會釋放的,即使它空閑,這就會產(chǎn)生性能問題,如果線程池的大小為200,當全部使用完畢后,所有的線程會繼續(xù)留在池中,相應的內(nèi)存和線程切換都會增加。如果要避免這個問題,就必須直接使用ThreadPoolExecutor()來構(gòu)造,設置“最大線程數(shù)”、“最小線程數(shù)”和“空閑線程存活的時間”。
    為了線程池能按時間計劃來執(zhí)行任務,允許用戶設定計劃執(zhí)行任務的時間,就要使用newScheduledThreadPool(int nThreads)方法返回ThreadPoolExecutor類的實例。參數(shù)nThreads是設定線程池中線程的最小數(shù)目,當任務較多時,線程池會自動創(chuàng)建更多的工作線程來執(zhí)行任務。其關鍵代碼如下:
    int nThreads=4 ;                //指定線程池尺寸
     //創(chuàng)建一個支持定時及周期性的任務執(zhí)行的線程池實例exec,池中含nThreads個線程
     Executor exec=Executors. newScheduledThreadPool(nThreads);
      Runnable task = new Runnable() { 
    public void run() { 
                  //查詢數(shù)據(jù)庫task表,有數(shù)據(jù)且執(zhí)行時間到了
                 //則另一個線程來執(zhí)行查詢到的任務
        } 
    };  
        //1 min后運行,并每隔2 min運行一次
    exec.scheduleAtFixedRate(task,60,60×2,TimeUnit.SECONDS);
3.4 執(zhí)行任務線程池的實現(xiàn)
    執(zhí)行任務的線程池采用newFixedThreadPool(int nThreads)方法建立,線程池具有固定數(shù)量。關鍵代碼如下:
    ExecutorService exec = Executors.newFixedThreadPool(2);
     Runnable run = new Runnable() {
          public void run() {
             //執(zhí)行任務
       }
       };
        exec.execute(run);
    對于任務的生產(chǎn)者,只需要向Task表中insert記錄即可,操作簡單。待執(zhí)行任務信息在可靠數(shù)據(jù)庫中保存,即使任務處理出了問題也不會讓未處理的任務信息丟失。
    本文利用Executor接口提供的異步任務執(zhí)行框架和任務執(zhí)行策略,實現(xiàn)多任務的執(zhí)行。在為具體應用線程池時往往需要根據(jù)應用的需求和處理任務的特點來優(yōu)化線程池的使用,設置合適的“最大線程數(shù)”、“最小線程數(shù)”和“空閑線程存活的時間”,采用不同的策略調(diào)整線程池的工作線程數(shù),才能達到最好的效果。
參考文獻
[1] 于國良.建立高性能擴展的Web應用系統(tǒng)[J].微計算機信息,2006,18(04):63-64.
[2] 鄭扣根.操作系統(tǒng)概論[M].北京:高等教育出版社,2004.
[3] 王華,馬亮,顧明.線程池技術研究與應用[J].計算機應用研究,2005(11):141-145.
[4] Sun Microsystems Inc. Java Platform,Standard Edition 6 API Specification[EB/OL].(2011-12-20).http://java.sun.com/javase/6/docs/api/index.Html,2008.

此內(nèi)容為AET網(wǎng)站原創(chuàng),未經(jīng)授權禁止轉(zhuǎn)載。