一種面向作業流(工作流)的輕量級可複用的異步流水開發框架JobFlow的設計與實現

一種面向作業流(工作流)的輕量級可複用的異步流水開發框架JobFlow的設計與實現

 

       要做這個東東的想法由來以久了的說,一個月以前動手把代碼實現了下來,今天覺得如果實在不寫成博文記錄下來的話,不知又要推到何年何月了。嗯,廢話不多說,直入正題了~

       在實際的開發過程中,我們經常會遇到這樣的情況:海量的併發用戶發送請求要求服務器處理,而服務器集羣之間彼此也經常會傳遞請求,用於完成特定的任務。我們可以把處理每個請求的過程理解爲是一種任務的執行,這樣的話,就相當於有一堆的作業擺在那時需要執行。而我們所要關心的是作業的數據結構是如何描述的、作業是如何存放、如何取出的以及是如何執行的。

       基於上述的思路,我們可以將任何的操作流程封裝在一個Job,並將其丟入到一個Job池中,Job池最好設置成阻塞模式,這樣即可喚醒等待的多個處理器從Job池中取出Job並執行。這樣一個框架的形成就牽涉到了宏觀設計的問題:即JobJob池及處理器這三種最重要的資源是如何定義和組織的。JobFlow整體的框架結果如下圖所示:

 

爲了便於簡化開發過程,使用的開發語言爲Java,但這種設計思路是基於面向對象的,具備普適意義,感興趣的可以自己實現C++版本。代碼的組織結構截圖如下:

 

一、Job作業的設計

       Job即是對處理某個任務/作業執行流程的一種抽象,Job接口提供一個最重要的方法:run(),我們可以定義具體的Job類,實現run()方法,用於填充必要的業務邏輯,在run()執行過程中可能會產生新的Job。按照這樣的思路,我們可以在一個大Jobrun()方法定義相關的執行流程,從而派生出若干個小Job,繼而實現了對一個大Job的拆分過程,同時每小Job也還可以繼續做拆分

       如下圖所示,簡單地舉例:我們定義了加法Job、減法Job、乘法Job及除法Job,每種Job均實現了Job接口,對於run()方法做了對應的填充,分別實現了兩個數的相加、相減、相乘和相除。

 

二、作業池JobPool的設計

       既然生成了Job,那麼這些Job就需要進行存放。JobPool作爲Job作業存儲的載體,最爲關注的是Job的存入put(),和Job的取出get()。而Job池本身的存儲介質可以是多種多樣的:內存、數據庫或者是文件系統;Job池的取出策略也可以是多種多樣的:作業先進先出FIFO、作業先進後出FILO、大作業優先、小作業優先、響應比高者優先、隨機等等。

       由於JobPool的存儲介質和取出策略兩者是獨立的部分,使用橋接模式代替類之間的繼承關係,使這兩個部分獨立地變化,從而避免了派生類的膨脹。

爲了便於說明問題,本文後面的代碼裏只關注了取出策略的變化,存儲介質只是基於內存實現的,但這並不代表我不知道如何實現這個橋接模式。

三、處理器Executer的設計

       有了Job,也有了存儲JobJobPool,那麼我們就需要定義一些計算資源從JobPool裏出取一個Job,並執行Job.run()。我們將這種計算資源抽象爲Executer,即處理器。Executer可以是一個進程或者是一個線程,可以是本地的計算資源,也可以考慮是遠程的計算資源,這樣我們可以靈活地將單機環境的系統擴展爲一個分佈式計算系統。爲了簡單處理,我們Executer的設計先考慮本地線程的情況。

我們在使用計算資源Executer時,並非只有一個Executer,利用ExecuterPoolExecuter進行管理。

 

爲了使計算資源也作業本身對接,我們創建一個作業池管理類JobPoolManager封裝JobPoolExecuterPool,用於取出Job並執行。

四、基本的實現BaseXXXX.java

有了上述這些框架基礎的接口之後,我們需要對這些接口做一些最初步的實現,稱之爲BaseXXXX.java,對應源代碼分別如下:

五、具體的實現

基於BaseXXXX.java的基本實現,我們需要定義能夠在實際運行環境下工作的類,源代碼如下:

 

六、基於隊列的JobPool實現

我們現在可以自己定義具體的Job存取方式了,如以List隊列形式存放Job會遵循FIFO的取出規則:

七、單機環境測試

爲了對框架本身進行簡單的測試,我們編寫了4個簡單的Job作業,分別做加減乘除運算,並編寫了一個Test類用於框架的執行:

八、將JobFlow的應用擴展到分佈式環境

目前而言,我們已經初步實現了單機環境下的異步工作流執行,我們更希望是將其擴展到一個分佈式的環境中,利用多物理處理機異步地執行整個工作流序列。設計的思想是將JobPool所封裝的Job隊列設置爲分佈式環境下共享的阻塞隊列,如果JobPool爲空,則所有處理器均處於阻塞狀態,如果有Job存入JobPool,則喚醒等待的處理器進行異步地執行。在編碼實現上,利用JavaRMI技術實現JobPool的遠程調用。

運行的方式:先運行RmiJobPoolInit使JobPool遠程對象化,從而適用於分佈式環境下併發的訪問,再在同一主控節點上運行RmiJobProducer產生若干Job並將這些Job放入到JobPool中。隨後,在其他從屬節點上運行RmiExecuterServer用於從JobPool中取出Job並執行,從而實現了一個簡單的分佈式計算框架。

       然而,這裏會存在一個問題,即如果你在從屬節點上先開啓RmiExecuterServer,而主控節點後開啓RmiJobProducer,則從屬節點還是一直處於阻塞狀態,並不會從JobPool中取Job執行,很奇怪呢。這裏Job的同步的控制並不完備,神馬原因呢,暫時還沒想通,以後我得找時間好好去調調~

      

九、總結

很明顯可以看出,整個JobFlow的框架的思想精髓的實質即是我們之前所學習的操作系統的兩個經典問題:生產者/消費者問題、作業調度問題。JobFlow適用於很多的場合,比如C/S模式的網絡收發數據包:網絡數據都需要經過打包、發包、解包、執行這四個流程,我們就可以將它們封裝成一個一個的Job,丟到一個很大的Job池裏面。Client端有多個打包線程不斷地將數據打包後丟到Job池中,又有多個發送線程去Job池裏取包向Server發送數據;Server端有多個接包線程在接收到Client所傳輸的數據包後,丟到自己Job池中,再由多個解析線程取Job進行數據包的解析,最後將解析後的Job分類,丟給相應的執行Job池中,再分別由多個執行線程從對應的執行Job池中取Job進行執行。

這裏只是舉例說明,具體的JobFlow的實現還是會遇到很多技術難點,要考慮各種Executer的同步、內存泄露問題、線程安全問題、各種池化對象的維護與管理。而對於一個框架而言,可擴展性、可複用性和靈活可配置性都是要考慮的問題。

       好了,JobFlow的原理和代碼實現大概就是如此了。爲了驗證想法,這個版本JobFlow的代碼實現的還是過於簡單了些,在最初設計上的考慮,在本版本的代碼裏並未。更爲完善的版本會在後續的時間裏更新,請大家持續關注本博客~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章