1. 程式人生 > >規則引擎Drools

規則引擎Drools

1. 規則引擎

規則引擎是一種巢狀在應用程式中的元件,它實現了將業務規則從應用程式程式碼中分離出來。規則引擎使用特定的語法編寫業務規則,規則引擎可以接受資料輸入、解釋業務規則、並根據業務規則做出相應的決策。


引入規則引擎後帶來的好處:

實現業務邏輯與業務規則的分離,實現業務規則的集中管理
可以動態修改業務規則,從而快速響應需求變更
使業務分析人員也可以參與編輯、維護系統的業務規則
使用規則引擎提供的規則編輯工具,使複雜的業務規則實現變得的簡單

2. 適合使用規則引擎的場景

case 1: 有一定量的業務邏輯處理,很多應用隨著時間推移越來越複雜,邏輯引擎可以更輕鬆應對。

只是把資料從資料庫中讀出寫入,就不要使用規則引擎。

case 2: application生命週期長適於規則引擎,短則不適合。

case 3: 業務需求頻繁變化適合使用規則引擎,否則不成立。

3. Drools

開源規則引擎的代表是Drools,商業規則引擎的代表是ILog。Drools是Jboss公司旗下一款開源的規則引擎,它完整的實現了Rete演算法;提供了強大的Eclipse Plugin開發支援;通過使用其中的DSL(Domain Specific Language),可以實現用自然語言方式來描述業務規則,使得業務分析人員也可以看懂業務規則程式碼。

3.1 Rete演算法 

RETE演算法是一個用來實現產生式規則系統的高效模式匹配演算法。它從一個初始的事實出發,不斷地應用規則得出結論(或執行指定的動作)。

3.2 規則的工作流程  

推理引擎包括三部分:Pattern Matcher、Agenda和Execution Engine。推理引擎通過Pattern Matcher決定執行哪些滿足事實或目的規則,並授予規則優先順序,這些規則被加入AgendaAgenda管理Pattern Matcher挑選出來的規則的執行次序;Execution Engine擎負責執行規則和其他動作。 

step 1. 將初始資料fact輸入Working Memory。 
step 2. 使用Pattern Matcher比較rule base中的rule和fact。

step 3. 如果rule存在衝突conflict,即同時激活了多個規則,將衝突的規則放入衝突集合。    

step 4. 解決衝突,將啟用的規則按順序放入Agenda。   

step 5. 使用Execution Engine執行Agenda中的規則。

重複步驟2至5,直到執行完畢所有Agenda中的規則。

4. Drools開發

4.1 規則

4.1.1 規則檔案以.drl結尾,可以存放多個規則,還可以存放被規則使用的使用者自定義的函式、資料物件及自定義查詢等, 對於同一package 下的使用者自定義函式、自定義的查詢等,不管這些函式與查詢是否在同一個規則檔案裡面,在規則裡面是可以直接使用。

package package-name ,宣告package在規則檔案中是必要的

imports 

globals 

functions 

queries 

rules 

e.g.

package storm.cookbook.log.rules

import storm.cookbook.log.model.LogEntry;
import java.util.regex.Matcher
import java.util.regex.Pattern

rule "Host Correction"

    when
        l: LogEntry(sourceHost == "localhost")
    then
        l.setSourceHost("localhost.example.com");
end

4.1.2 一個rule通常包括三個部分:屬性部分(attribute)、條件部分(LHS)和結果部分(RHS)。對於一個完整的規則來說,這三個部分都是可選的。

LHS 部分是由一個或多個條件組成,條件又稱之為pattern(匹配模式),多個pattern之間用可以使用and 或or 來進行連線,同時還可以使用小括號來確定pattern 的優先順序。 

RHS 為條件滿足觸發的動作,可以使用LHS 部分當中定義的繫結變數名、設定的全域性變數、或者是直接編寫Java 程式碼(import 引入的Java類)

規則屬性是用來控制規則執行的重要工具,在目前的Drools5當中,規則的屬性共有13個,它們分別是:activation-group、agenda-group、auto-focus、date-effective、date-expires、dialect、duration、enabled、lock-on-active、no-loop、ruleflow-group、salience、when

salience:用來設定規則執行的優先順序,salience屬性的值是一個數字,數字越大執行優先順序越高,同時它的值可以是一個負數。預設情況下,規則的salience預設值為0。

no-loop:   在條件滿足時,對Working Memory當中的某個Fact物件進行了修改,將其更新到當前的Working Memory中,這時引擎會再次檢查所有的規則是否滿足條件,no-loop屬性控制如果滿足是否會再次執行。no-loop屬性的值是一個布林型,預設為false。

date-effective: 當系統時間>=date-effective設定的時間值時,規則才會觸發,否則規則將不執行。若沒有設定該屬性,規則將隨時觸發。該屬性的值是一個日期型別,預設格式是”dd-MMM-yyyy”。可以通過設定改變格式。

date-expires: 如果系統時間<date-expires設定的時間值,那麼規則將執行,否則就不執行。

activation-group: 將若干個規則劃分成一個組,具有相同activation-group屬性的規則只要有一個被執行,其它的規則都將不再執行。規則當中究竟哪一個會先執行,可以用類似salience之類屬性來實現。

agenda-group: 規則的呼叫與執行是通過StatelessSession或StatefulSession來實現的,一般的順序是建立一個StatelessSession或StatefulSession,將各種經過編譯的規則的package新增到session當中,接下來規則當中可能用的Global物件和Fact物件插入到Session當中,最後呼叫fireAllRules方法來執行,在呼叫fireAllRules方法之前,所有的規則及插入的Fact物件都存放在一個名叫Agenda表的物件當中。agenda-group是用來在Agenda的基礎上,對現的規則進行再次分組。Agenda-group屬性的值也是一個字串,引擎在呼叫這些設定了agenda-group屬性的規則的時候需要顯示的指定某個Agenda Group得到Focus(焦點),這樣位於Agenda Group當中的規則才會觸發執行,否則將不執行。

auto-focus:  在agenda-group的規則上設定該規則是否可以自動讀取Focus,如果該屬性設定為true,則在引擎執行時,不需要顯示的為某個Agenda Group設定Focus,否則需要。

dialect:  定義規則當中要使用的語言型別,目前Drools5版本當中支援兩種型別的語言:mvel和java,預設情況下,如果沒有手工設定規則的dialect,那麼使用的是java語言。

duration:  對於一個規則來說,如果設定了該屬性,那麼規則將在該屬性指定的值之後在另外一個執行緒裡觸發。該屬性對應的值為一個長整型,單位是毫秒。

enabled: 定義一個規則是否可用的, 該屬性的值是一個布林值,預設為true,表示規則是可用的。如果手工為一個規則新增一個enabled屬性,並且該屬性值為false,那麼引擎就不會執行該規則。

e.g.

rule "rule1"

   salience 1 / duration 3000

   when 

       eval(true)

  then

       System.out.println("rule1");


4.2  Java程式開發

在Drools當中,規則的編譯與執行要通過Drools提供的各種API來實現,這些API總體來講可以分為三類:規則編譯、規則收集和規則的執行。完成這些工作的API主要有KnowledgeBuilder、KnowledgeBase、StatefulKnowledgeSession、StatelessKnowledgeSession等,它們起到了對規則檔案進行收集、編譯、查錯、插入fact、設定global、執行規則或規則流等作用,在正式接觸各種型別的規則檔案編寫方式及語法講解之前,我們有必要先熟悉一下這些API的基本含義及使用方法。

4.2.1 KnowledgeBuilder

規則編寫完成之後,接下來的工作就是在應用的程式碼當中呼叫這些規則,利用這些編寫好的規則幫助我們處理業務問題。KnowledgeBuilder 的作用就是用來在業務程式碼當中收集已經編寫好的規則, 然後對這些規則檔案進行編譯, 最終產生一批編譯好的規則包(KnowledgePackage)給其它的應用程式使用。KnowledgeBuilder 在編譯規則的時候可以通 過其提供的hasErrors()方法得到編譯規則過程中發現規則是否有錯誤,如果有的話通過其提 供的getErrors()方法將錯誤打印出來,以幫助我們找到規則當中的錯誤資訊。

通過KnowledgeBuilder編譯的規則檔案的型別可以有很多種,如.drl檔案、.dslr檔案或一個xls檔案等。產生的規則包可以是具體的規則檔案形成的,也可以是規則流(rule flow)檔案形成的,在新增規則檔案時,需要通過使用ResourceType的列舉值來指定規則檔案的型別;同時在指定規則檔案的時候drools還提供了一個名為ResourceFactory的物件,通過該物件可以實現從Classpath、URL、File、ByteArray、Reader或諸如XLS的二進位制檔案裡新增載規則。

建立KnowledgeBuilder 物件使用的是KnowledgeBuilderFactory 的newKnowledgeBuilder方法。以下是程式碼 
         
kbuilder.add(ResourceFactory.newClassPathResource("Sample.drl"), ResourceType.DRL);           

KnowledgeBuilderErrors errors = kbuilder.getErrors();              

if (errors.size() > 0) { 
                  for (KnowledgeBuilderError error : errors) {                        

                                         System.err.println(error);                  

                  } 
                  throw new IllegalArgumentException("Could not parse knowledge."); 
 

Collection<KnowledgePackage>  kpackage=kbuilder.getKnowledgePackages();//產生規則包的集合

4.2.2 KnowledgeBase 

KnowledgeBase 是Drools 提供的用來收集應用當中知識(knowledge)定義的知識庫物件,在一個KnowledgeBase 當中可以包含普通的規則(rule)、規則流(rule flow)、函式定義(function)、使用者自定義物件(type model)等。KnowledgeBase 本身不包含任何業務資料物件,業務物件都是插入到由KnowledgeBase產生的兩種型別的session 物件當中,通過session 物件可以觸發規則執行或開始一個規則流執行。 
建立一個KnowledgeBase 要通過KnowledgeBaseFactory 物件提供的newKnowledgeBase()方法來實現。
KnowledgeBase kbase=KnowledgeBaseFactory.newKnowledgeBase();  

建立的時候還可以為其指定一個KnowledgeBaseConfiguration物件,KnowledgeBaseConfiguration物件是一個用來存放規則引擎執行時相關環境引數定義的配置物件。

KnowledgeBaseConfiguration kbConf = KnowledgeBaseFactory.newKnowledgeBaseConfiguration(); 
kbConf.setProperty( "org.drools.sequential", "true");   

KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(kbConf);
KnowledgeBase 建立完成之後,接下來就可以將我們前面使用KnowledgeBuilder 生成的KnowledgePackage 的集合新增到KnowledgeBase 當中,以備使用,以下是程式碼 kbase.addKnowledgePackages(kpackage);

4.2.3 StatefulKnowledgeSession 
規則編譯完成之後,接下來就需要使用一個API 使編譯好的規則包檔案在規則引擎當中執行起來。在Drools5 當中提供了兩個物件與規則引擎進行互動:StatefulKnowledgeSession和StatelessKnowledgeSession。 
StatefulKnowledgeSession 物件是一種最常用的與規則引擎進行互動的方式,它可以與規則引擎建立一個持續的互動通道,在推理計算的過程當中可能會多次觸發同一資料集。在使用者的程式碼當中,最後使用完StatefulKnowledgeSession 物件之後,一定要呼叫其dispose()方法以釋放相關記憶體資源。 
StatefulKnowledgeSession 可以接受外部插入(insert)的業務資料——也叫fact,一個fact 物件通常是一個普通的Java 的POJO,一般它們會有若干個屬性,每一個屬性都會對應getter 和setter 方法,用來對外提供資料的設定與訪問。一般來說,在Drools 規則引擎當中,fact 所承擔的作用就是將規則當中要用到的業務資料從應用當中傳入進來,對於規則當中產生的資料及狀態的變化通常不用fact 傳出。如果在規則當中需要有資料傳出,那麼可以通過在StatefulKnowledgeSession 當中設定global 物件來實現,一個global 物件也是一個普通的Java 物件,在向StatefulKnowledgeSession 當中設定global 物件時不用insert 方法而用setGlobal 方法實現。建立一個StatefulKnowledgeSession 要通過KnowledgeBase 物件來實現,以下是程式碼 
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); 

KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "test"); 
   // go ! 
Message message = new Message();    

message.setMessage("Hello World");    

message.setStatus(Message.HELLO);    

ksession.insert(message);  
ksession.fireAllRules();  
logger.close(); 

ksession.dispose(); 
上面的所有程式碼表示了規則完整的執行處理過程,可以看到,它的過程是首先需要通過使用KnowledgeBuilder 將相關的規則檔案進行編譯,產生對應的KnowledgePackage集合,接下來再通過KnowledgeBase 把產生的KnowledgePackage 集合收集起來,最後再產生StatefulKnowledgeSession 將規則當中需要使用的fact 物件插入進去、將規則當中需要用到的global 設定進去,然後呼叫fireAllRules()方法觸發所有的規則執行,最後呼叫dispose()方法將記憶體資源釋放。

4.2.4 StatelessKnowledgeSession 
StatelessKnowledgeSession 的作用與StatefulKnowledgeSession 相仿,它們都是用來接收業務資料、執行規則的。事實上,StatelessKnowledgeSession 對StatefulKnowledgeSession 做了包裝,使得在使用StatelessKnowledgeSession 物件時不需要再呼叫dispose()方法釋放記憶體資源了。因為StatelessKnowledgeSession 本身所具有的一些特性,決定了它的使用有一定的侷限性。在使用StatelessKnowledgeSession 時不能進行重複插入fact 的操作、也不能重複的呼叫fireAllRules()方法來執行所有的規則,對應這些要完成的工作StatelessKnowledgeSession當中只有execute(…)方法,通過這個方法可以實現插入所有的fact 並且可以同時執行所有的規則或規則流,事實上也就是在執行execute(…)方法的時候就在StatelessKnowledgeSession內部執行了insert()方法、fireAllRules()方法和dispose()方法。以下是使用程式碼 
StatelessKnowledgeSession  statelessKSession=kbase.newStatelessKnowledgeSession(); 

ArrayList list=new ArrayList(); 

list.add(new Object());

list.add(new Object());

statelessKSession.execute(list); 

4.2.5 Fact 
Fact 是指在Drools 規則應用當中,將一個普通的JavaBean 插入到規則的WorkingMemory當中後的物件。規則可以對Fact 物件進行任意的讀寫操作,當一個JavaBean 插入到WorkingMemory 當中變成Fact 之後,Fact 物件不是對原來的JavaBean 物件進行Clon,而是原來JavaBean 物件的引用。規則在進行計算的時候需要用到應用系統當中的資料,這些資料設定在Fact 物件當中,然後將其插入到規則的WorkingMemory 當中,這樣在規則當中就可以通過對Fact 物件資料的讀寫,從而實現對應用資料的讀寫操作。一個Fact 物件通常是一個具有getter 和setter 方法的POJO 物件,通過這些getter 和setter 方法可以方便的實現對Fact 物件的讀寫操作,所以我們可以簡單的把Fact 物件理解為規則與應用系統資料互動的橋樑或通道。當 Fact 物件插入到WorkingMemory 當中後,會與當前WorkingMemory 當中所有的規則進行匹配,同時返回一個FactHandler 物件。FactHandler 物件是插入到WorkingMemory當中Fact 物件的引用控制代碼,通過FactHandler 物件可以實現對對應的Fact 物件的刪除及修改等操作。 
在前面介紹StatefulKnowledgeSession 和StatelessKnowledgeSession 兩個物件的時候也提到了插入Fact 物件的方法,在StatefulKnowledgeSession 當中直接使用insert 方法就可以將一個Java 物件插入到WokingMemory 當中,如果有多個Fact 需要插入,那麼多個呼叫insert方法即可;對於StatelessKnowledgeSession 物件可利用CommandFactory 實現單個Fact 物件或多個Fact 物件的插入。