小明歷險記:規則引擎Drools教程詳解
小明是一家網際網路公司的軟體工程師,他們公司為了吸引新使用者經常會搞活動,小明常常為了做活動加班加點很煩躁,這不今天呀又來了一個活動需求,我們大家一起幫他看看。
小明的煩惱
活動規則是根據使用者購買訂單的金額給使用者送相應的積分,購買的越多送的積分越多,使用者可以使用積分來兌換相應的商品,我們這次活動的力度很大,肯定會吸引很多的使用者參加,產品經理小王興高采烈唾液橫飛的對小明講到。小明心想,又tm來這套,這次需求又要變更多少次呢?表面上還的配合,說趕緊把規則給我們吧,早點開發早點上線,小王說這次需求老簡單啦,估計你們兩個小時就搞定了,不信你看需求文件。
使用者購買的金額和對應送多少積分的規則如下:
100元以下, 不加分
100元-500元 加100分
500元-1000元 加500分
1000元 以上 加1000分
小明一看,這需求果然簡單呀,作為一個工作了兩三年的程式設計師來講,這不就是小case,半天搞定,送積分的心程式碼如下:
public void execute() throws Exception { List<Order> orderList = getInitData(); for (int i=0; i<orderList.size(); i++){ Order order = orderList.get(i); if (order.getAmout() <= 100){ order.setScore(0); addScore(order); }else if(order.getAmout() > 100 && order.getAmout() <= 500){ order.setScore(100); addScore(order); }else if(order.getAmout() > 500 && order.getAmout() <= 1000){ order.setScore(500); addScore(order); }else{ order.setScore(1000); addScore(order); } } }
上線運行了半天之後,財務部的小財突然監測到活動賬戶的金額大為減少,發現產品做活動竟然沒有通知到他,非常不爽,於是給領導小馬說,這樣大規模的活動,對公司財務有壓力,領導小馬權衡了一番說,這樣吧活動繼續,但是金額翻倍在送積分,於是規則變成了這樣:200元以下不給積分,1000元以下給100積分...
小明看領導都發話了,沒辦法改呀,不過也簡單,就是將裡面的值都翻了倍,在投產上去,只是捱了不少測試的白眼。
活動又進行了一天,運營人員通過後臺監控發現提到2倍以後,使用者積極性變的很差,活動效果不理想,和領導商議了一下,改為最初規則的1.5倍,及150元一下不給積分,750元以下給給100積分... 小明這時候的心情大概是這樣子的,一萬個下圖動物狂奔而過。
沒辦法還的改不是,當然這次小明可學乖了,將這些資料(多少元送多少分)存到了資料庫中,當老闆在改主意的時候,只要改一下資料庫的值就可以了,小明為自己的明聰感到有點小高興。
核心程式碼程式設計了這樣
public void execute() throws Exception {
List<Order> orderList = getInitData();
List<int> values = getTableValues();
for (int i=0; i<orderList.size(); i++){
Order order = orderList.get(i);
if (order.getAmout() <= values.get(0)){
order.setScore(values.get(3));
addScore(order);
}else if(order.getAmout() > values.get(0) && order.getAmout() <= values.get(1)){
order.setScore(values.get(4));
addScore(order);
}else if(order.getAmout() > values.get(1) && order.getAmout() <= values.get(2)){
order.setScore(values.get(5));
addScore(order);
}else{
order.setScore(values.get(6));
addScore(order);
}
}
}
正當小明得意洋洋的打個了最新版本投產上線之後,產品經理小王說積分規則層次太少了,由以前的4組變成8組,小明此刻的心情:kao ...
小明想這樣下去非得被他們弄死,必須要找找有什麼技術可以將活動規則和程式碼解耦,不管規則如何變化,執行端不用動。小明搜了半天還真有這樣的東西,那就是規則引擎,那麼規則引擎到底是什麼東西呢?我們來看看。
規則引擎
相關介紹
規則引擎起源於基於規則的專家系統,而基於規則的專家系統又是專家系統的其中一個分支。專家系統屬於人工智慧的範疇,它模仿人類的推理方式,使用試探性的方法進行推理,並使用人類能理解的術語解釋和證明它的推理結論。
利用它就可以在應用系統中分離商業決策者的商業決策邏輯和應用開發者的技術決策,並把這些商業決策放在中心資料庫或其他統一的地方,讓它們能在執行時可以動態地管理和修改,從而為企業保持靈活性和競爭力提供有效的技術支援。
在需求裡面我們往往把約束,完整性,校驗,分支流等都可以算到業務規則裡面。在規則引擎裡面談的業務規則重點是談當滿足什麼樣的條件的時候,需要執行什麼樣的操作。因此一個完整的業務規則包括了條件和觸發操作兩部分內容。而引擎是事物內部的重要的執行機制,規則引擎即重點是解決規則如何描述,如何執行,如何監控等一系列問題。
規則引擎由推理引擎發展而來,是一種嵌入在應用程式中的元件,實現了將業務決策從應用程式程式碼中分離出來,並使用預定義的語義模組編寫業務決策。接受資料輸入,解釋業務規則,並根據業務規則做出業務決策。
java開源的規則引擎有:Drools、Easy Rules、Mandarax、IBM ILOG。使用最為廣泛並且開源的是Drools。
規則引擎的優點
-
宣告式程式設計
規則可以很容易地解決困難的問題,並得到解決方案的驗證。與程式碼不同,規則以較不復雜的語言編寫; 業務分析師可以輕鬆閱讀和驗證一套規則。 -
邏輯和資料分離
資料位於“域物件”中,業務邏輯位於“規則”中。根據專案的種類,這種分離是非常有利的。 -
速度和可擴充套件性
寫入Drools的Rete OO演算法已經是一個成熟的演算法。在Drools的幫助下,您的應用程式變得非常可擴充套件。如果頻繁更改請求,可以新增新規則,而無需修改現有規則。 -
知識集中化
通過使用規則,您建立一個可執行的知識庫(知識庫)。這是商業政策的一個真理點。理想情況下,規則是可讀的,它們也可以用作文件。
rete 演算法
Rete 演算法最初是由卡內基梅隆大學的 Charles L.Forgy 博士在 1974 年發表的論文中所闡述的演算法 , 該演算法提供了專家系統的一個高效實現。自 Rete 演算法提出以後 , 它就被用到一些大型的規則系統中 , 像 ILog、Jess、JBoss Rules 等都是基於 RETE 演算法的規則引擎
Rete 在拉丁語中譯為”net”,即網路。Rete 匹配演算法是一種進行大量模式集合和大量物件集合間比較的高效方法,通過網路篩選的方法找出所有匹配各個模式的物件和規則。
其核心思想是將分離的匹配項根據內容動態構造匹配樹,以達到顯著降低計算量的效果。Rete 演算法可以被分為兩個部分:規則編譯和規則執行。當Rete演算法進行事實的斷言時,包含三個階段:匹配、選擇和執行,稱做 match-select-act cycle。
rate演算法的詳細內容可以參考這篇文章:開源規則流引擎實踐
Drools 介紹
Drools 是一個基於Charles Forgy's的RETE演算法的,易於訪問企業策略、易於調整以及易於管理的開源業務規則引擎,符合業內標準,速度快、效率高。
業務分析師人員或稽核人員可以利用它輕鬆檢視業務規則,從而檢驗是否已編碼的規則執行了所需的業務規則。
Drools 是用Java語言編寫的開放原始碼規則引擎,使用Rete演算法對所編寫的規則求值。Drools允許使用宣告方式表達業務邏輯。可以使用非XML的本地語言編寫規則,從而便於學習和理解。並且,還可以將Java程式碼直接嵌入到規則檔案中,這令Drools的學習更加吸引人。
Drools優點:
- 非常活躍的社群支援
- 易用
- 快速的執行速度
- 在 Java 開發人員中流行
- 與 Java Rule Engine API(JSR 94)相容
Drools相關概念:
- 事實(Fact):物件之間及物件屬性之間的關係
- 規則(rule):是由條件和結論構成的推理語句,一般表示為if...Then。一個規則的if部分稱為LHS,then部分稱為RHS。
- 模式(module):就是指IF語句的條件。這裡IF條件可能是有幾個更小的條件組成的大條件。模式就是指的不能在繼續分割下去的最小的原子條件。
Drools通過 事實、規則和模式相互組合來完成工作,drools在開源規則引擎中使用率最廣,但是在國內企業使用偏少,保險、支付行業使用稍多。
小明看了這麼多概念,有點暈,麼關係,來一個特別簡單的示例幫忙解決小明的問題。
解決小明的煩惱
drools有專門的規則語法drl,就是專門描述活動的規則是如何執行的,按照小明的需求規則如下:
Point-rules.drl 檔案內容
package rules
import com.neo.drools.entity.Order
rule "zero"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout <= 100)
then
$s.setScore(0);
update($s);
end
rule "add100"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 100 && amout <= 500)
then
$s.setScore(100);
update($s);
end
rule "add500"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 500 && amout <= 1000)
then
$s.setScore(500);
update($s);
end
rule "add1000"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > 1000)
then
$s.setScore(1000);
update($s);
end
- package 與Java語言類似,drl的頭部需要有package和import的宣告,package不必和物理路徑一致。
- import 匯出java Bean的完整路徑,也可以將Java靜態方法匯入呼叫。
- rule 規則名稱,需要保持唯一 件,可以無限次執行。
- no-loop 定義當前的規則是否不允許多次迴圈執行,預設是 false,也就是當前的規則只要滿足條件,可以無限次執行。
- lock-on-active 將lock-on-active屬性的值設定為true,可避免因某些Fact物件被修改而使已經執行過的規則再次被啟用執行。
- salience 用來設定規則執行的優先順序,salience 屬性的值是一個數字,數字越大執行優先順序越高, 同時它的值可以是一個負數。預設情況下,規則的 salience 預設值為 0。如果不設定規則的 salience 屬性,那麼執行順序是隨機的。
- when 條件語句,就是當到達什麼條件的時候
- then 根據條件的結果,來執行什麼動作
- end 規則結束
這個規則檔案就是描述了,當符合什麼條件的時候,應該去做什麼事情,每當規則有變動的時候,我們只需要修改規則檔案,然後重新載入即可生效。
這裡需要有一個配置檔案告訴程式碼規則檔案drl在哪裡,在drools中這個檔案就是kmodule.xml,放置到resources/META-INF目錄下。
kmodule.xml內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="point-rulesKB" packages="rules">
<ksession name="point-rulesKS"/>
</kbase>
</kmodule>
以下對配置說明進行簡單說明:
- Kmodule 中可以包含一個到多個 kbase,分別對應 drl 的規則檔案。
- Kbase 需要一個唯一的 name,可以取任意字串。
- packages 為drl檔案所在resource目錄下的路徑。注意區分drl檔案中的package與此處的package不一定相同。多個包用逗號分隔。預設情況下會掃描 resources目錄下所有(包含子目錄)規則檔案。
- kbase的default屬性,標示當前KieBase是不是預設的,如果是預設的則不用名稱
就可以查詢到該 KieBase,但每個 module 最多隻能有一個預設 KieBase。 - kbase 下面可以有一個或多個 ksession,ksession 的 name 屬性必須設定,且必須唯一。
再看看程式碼端怎麼處理,貼出核心程式碼
public static final void main(final String[] args) throws Exception{
KieServices ks = KieServices.Factory.get();
KieContainer kc = ks.getKieClasspathContainer();
execute( kc );
}
public static void execute( KieContainer kc ) throws Exception{
KieSession ksession = kc.newKieSession("point-rulesKS");
List<Order> orderList = getInitData();
for (int i = 0; i < orderList.size(); i++) {
Order o = orderList.get(i);
ksession.insert(o);
ksession.fireAllRules();
addScore(o);
}
ksession.dispose();
}
程式碼解釋:首先通過請求獲取 KieServices,通過KieServices獲取KieContainer,KieContainer載入規則檔案並獲取KieSession,KieSession來執行規則引擎,KieSession是一個輕量級組建,每次執行完銷燬。KieContainer是重量級組建可以考慮複用。
OK 小明的需求,程式碼部分這樣寫就行了,完全不用考慮以後的規則變化了。當活動的規則有變化的時候,小明只要修改規則檔案Point-rules.drl中下方相關規則內容既可,如果活動規則動態的新增、減少也可以相應的去增加、減少規則檔案既可,再也不用去動程式碼了。
rule "xxx"
no-loop true
lock-on-active true
salience 1
when
$s : Order(amout > yy)
then
$s.setScore(yy);
update($s);
end
看到這裡小明又開始哼起了歌曲。。。