小明是一家互聯網公司的軟件工程師,他們公司為了吸引新用戶經常會搞活動,小明常常為了做活動加班加點很煩躁,這不今天呀又來了一個活動需求,我們大家一起幫他看看。
小明的煩惱
活動規則是根據用戶購買訂單的金額給用戶送相應的積分,購買的越多送的積分越多,用戶可以使用積分來兌換相應的商品,我們這次活動的力度很大,肯定會吸引很多的用戶參加,產品經理小王興高采烈唾液橫飛的對小明講到。小明心想,又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
看到這里小明又開始哼起了歌曲。。。
本篇文章算是對drools的簡單介紹,后續文章將詳細介紹drools的使用。
參考:
專欄:drools規則引擎
文章列表