從0到1:構建強大且易用的規則引擎
優(yōu)采云 發(fā)布時(shí)間: 2022-06-18 20:24從0到1:構建強大且易用的規則引擎
2016 年 7 月恰逢美團點(diǎn)評的業(yè)務(wù)進(jìn)入“下半場(chǎng)”,需要在各個(gè)環(huán)節優(yōu)化體驗、提升效率、降低成本。技術(shù)團隊需要怎么做來(lái)適應這個(gè)變化?這個(gè)問(wèn)題直接影響著(zhù)之后的工作思路。
美團外賣(mài)的 CRM 業(yè)務(wù)步入成熟期,規則類(lèi)需求幾乎撐起了這個(gè)業(yè)務(wù)所有需求的半邊天。
一方面規則唯一不變的是“多變”,另一方面開(kāi)發(fā)團隊對“規則開(kāi)發(fā)”的感受是乏味、疲憊和缺乏技術(shù)含量。如何解決規則開(kāi)發(fā)的效率問(wèn)題,最大化解放開(kāi)發(fā)團隊成為目前的一個(gè) KPI。
規則引擎作為常見(jiàn)的維護策略規則的框架很快進(jìn)入我的思路。它能將業(yè)務(wù)決策邏輯從系統邏輯中抽離出來(lái),使兩種邏輯可以獨立于彼此而變化,這樣可以明顯降低兩種邏輯的維護成本。
分析規則引擎如何設計正是本文的主題,過(guò)程中也簡(jiǎn)單介紹了實(shí)現方案。
美團規則引擎應用實(shí)踐
首先回顧幾個(gè)美團點(diǎn)評的業(yè)務(wù)場(chǎng)景,通過(guò)這些場(chǎng)景大家能更好地理解什么是規則,規則的邊界是什么。
在每個(gè)場(chǎng)景后面都介紹了業(yè)務(wù)系統現在使用的解決方案以及主要的優(yōu)缺點(diǎn)。
門(mén)店信息校驗
場(chǎng)景
美團點(diǎn)評合并前的美團平臺事業(yè)部中,門(mén)店信息入口作為門(mén)店信息的第一道關(guān)卡,有一個(gè)很重要的職責,就是質(zhì)量控制,其中第一步就是針對一些字段的校驗規則。
下面從流程的角度看下門(mén)店信息入口業(yè)務(wù)里校驗門(mén)店信息的規則模型(已簡(jiǎn)化),如下圖:
規則主體包括三部分:
方案:硬編碼
由于歷史原因,門(mén)店信息校驗采用了硬編碼的方式,偽代碼如下:
if (StringUtil.isBlank(fieldA)
|| StringUtil.isBlank(fieldB)
|| StringUtil.isBlank(fieldC)
|| StringUtil.isBlank(fieldD)) {
return ResultDOFactory.createResultDO(Code.PARAM_ERROR, "門(mén)店參數缺少必填項");
}if (fieldA.length() < 10) {
return ResultDOFactory.createResultDO(Code.PARAM_ERROR, "門(mén)店名稱(chēng)長(cháng)度不能少于10個(gè)字符");
}
if (!isConsistent(fieldB, fieldC, fieldD)) {
return ResultDOFactory.createResultDO(Code.PARAM_ERROR, "門(mén)店xxx地址、行政區和經(jīng)緯度不一致");
}
優(yōu)點(diǎn):
缺點(diǎn):
門(mén)店審核流程
場(chǎng)景
流程控制中心(負責在運行時(shí)根據輸入參數選擇不同的流程節點(diǎn)從而構建一個(gè)流程實(shí)例)會(huì )根據輸入門(mén)店信息中的渠道來(lái)源和品牌等特征確定本次審核(不)走哪些節點(diǎn),其中選擇策略的模型如下圖:
規則主體是分支條件:
方案:開(kāi)源 Drools 從入門(mén)到放棄
經(jīng)過(guò)一系列調研,團隊選擇基于開(kāi)源規則引擎 Drools 來(lái)配置流程中審核節點(diǎn)的選擇策略。使用 Drools 后的規則配置流程如下圖:
上圖中 DSL 即是規則主體,規則內容如下:
rule "1.1"
when
poi : POI( source == 1 && brandType == 1 )
then
System.out.println( "1.1 matched" );
poi.setPassedNodes(1);
end
rule "1.2"
when
poi : POI( source == 1 && brandType == 2 )
then
System.out.println( "1.2 matched" );
end
rule "2.1"
when
poi : POI( source == 2 && brandType == 1 )
then
System.out.println( "2.1 matched" );
poi.setPassedNodes(2);
end
rule "2.2"
when
poi : POI( source == 2 && brandType == 2 )
then
System.out.println( "2.2 matched" );
poi.setPassedNodes(3);
end
在實(shí)踐中,我們發(fā)現 Drools 方案有如下幾個(gè)優(yōu)缺點(diǎn),由于 Drools 的問(wèn)題較多,最后這個(gè)方案還是放棄了。
優(yōu)點(diǎn):
缺點(diǎn):
績(jì)效指標計算
場(chǎng)景
美團外賣(mài)業(yè)務(wù)發(fā)展非常迅速,績(jì)效指標規則需要快速迭代才能緊跟業(yè)務(wù)發(fā)展步伐???jì)效考核頻率是一個(gè)月一次,因此績(jì)效規則的迭代頻率也是每月一次。因為績(jì)效規則系統是硬編碼實(shí)現,因此開(kāi)發(fā)團隊需要投入大量的人力滿(mǎn)足規則更新需求。
2016 年 10 月底,我受績(jì)效團隊委托成立一個(gè)項目組,開(kāi)發(fā)部署了一套績(jì)效指標配置系統,系統上線(xiàn)直接減少了產(chǎn)品經(jīng)理和技術(shù)團隊 70% 的工作量。
下面我們首先分析下績(jì)效指標計算的規則模型,如下圖:
規則主體是結構化數據處理邏輯:
方案:業(yè)務(wù)定制規則引擎
績(jì)效規則主體是數據處理,但我們認為數據處理同樣屬于規則的范疇,因此我們將其放在本文進(jìn)行分析。
下圖是績(jì)效指標配置系統,觸發(fā)器負責定時(shí)驅動(dòng)引擎進(jìn)行計算;視圖負責給商業(yè)分析師提供規則配置界面,規則表達能力取決于視圖;引擎負責將配置的規則解析成 Spark 原語(yǔ)進(jìn)行計算。
優(yōu)點(diǎn):
缺點(diǎn):
探索全新設計
“案例”一節中三種落地方案的問(wèn)題總結如下:
由于“高效配置規則”是業(yè)務(wù)里長(cháng)期存在的剛需,且行業(yè)內又缺乏符合需求的解決方案,2017 年 2 月我在團隊內部設立了一個(gè)虛擬小組專(zhuān)門(mén)負責規則引擎的設計研發(fā)。
引擎設計指標是要覆蓋工作中基礎的規則迭代需求(包括但不限于“案例”一節中的多個(gè)場(chǎng)景),同時(shí)針對“案例”一節中已有解決方案揚長(cháng)避短。
下面分三節來(lái)重現這個(gè)項目的設計過(guò)程:
需求模型
對規則引擎來(lái)說(shuō),世界皆規則。通過(guò)“案例”一節的分析,我們對規則以及規則引擎該如何構建的思路正逐漸變得清晰。
下面兩節分別定義規則數據模型和規則引擎的系統模型,目標是對“Maze 框架”一節中的規則引擎產(chǎn)品進(jìn)行框架性指導。
規則數據模型
規則本質(zhì)是一個(gè)函數,由 n 個(gè)輸入、1 個(gè)輸出和函數計算邏輯 3 部分組成。
y = f(x1, x2, …, xn)
具體結合“案例”一節中的場(chǎng)景,我們梳理出的規則模型如下圖所示:
主要由三部分構成:
結果對象,規則處理完畢后的結果。需要支持自定義類(lèi)型或者簡(jiǎn)單類(lèi)型(Integer、Long、Float、Double、Short、String、Boolean 等)。
系統模型
我們需要設計一個(gè)系統能配置、加載、解釋執行上節中的數據模型,另外設計時(shí)還需要規避“案例”一節 3 個(gè)方案的缺點(diǎn)。最終我們定義了如下圖所示的系統模型。
主要由三個(gè)模塊構成:
資源管理器,負責管理規則。
最終結果 /** 變量模式 */
|
|
中間結果 > $參數3 /** 關(guān)系運算模式 */
|
|
$參數1 + $參數2 /** 算數運算模式 */
Maze 框架
基于"需求模型"一節的定義,我們開(kāi)發(fā)了 Maze 框架(Maze 是迷宮的意思,寓意:迷宮一樣復雜的規則)。
Maze 框架分兩個(gè)引擎:
其中 MazeGO 內解析到結構化數據處理模式會(huì )調用 SQLC 驅動(dòng) MazeQL 完成計算,比如:從數據庫里查詢(xún)某個(gè) BD 的月交易額,如果交易額超過(guò) 30 萬(wàn)則執行 A 邏輯否則執行 B 邏輯,這個(gè)語(yǔ)義的規則需要執行結構化查詢(xún)。
MazeQL 內解析到策略計算模式會(huì )調用 VectorC 驅動(dòng) MazeGO 進(jìn)行計算,比如:有一張訂單表,其中第一列是商品 ID,第二列是商品購買(mǎi)數量,第三列是此商品的單價(jià)。
我們需要計算每類(lèi)商品的總價(jià)則需要對結構化查詢(xún)到的結果的每一行執行第二列*第三列這樣的策略模式計算。
名詞解釋?zhuān)?/p>
SQLC 指結構化查詢(xún),擁有執行 SQL 的能力。
MazeGO
MazeGO 核心主要由三部分構成:
另外兩個(gè)輔助模塊是流量控制器和規則效果分析模塊,基本構成如下圖:
三個(gè)核心模塊(引擎、知識庫和資源管理器)的職責見(jiàn)“需求模型”一節中“系統模型”一節。
下面只介紹下和“系統模型”不同的部分:
預編譯規則實(shí)例,因為規則每次編譯執行會(huì )導致性能問(wèn)題,因此會(huì )在引擎初始化和規則有變更這兩個(gè)時(shí)機將增量版本的規則預編譯成可執行代碼。規則管理模塊。職責如下:
MazeQL
MazeQL 核心主要由三部分構成:
QL 驅動(dòng)器,驅動(dòng)平臺進(jìn)行規則計算。因為任務(wù)的實(shí)際執行平臺有多種(會(huì )在下一個(gè)“平臺”部分介紹),因此 QL 驅動(dòng)器也有多種實(shí)現。
預加載規則實(shí)例,首先為了避免訪(fǎng)問(wèn)規則時(shí)需要實(shí)時(shí)執行遠程調用而造成較大的時(shí)延,另外規則并不是時(shí)刻發(fā)生變更沒(méi)有必要每次訪(fǎng)問(wèn)時(shí)拉取一次最新版本。
基于以上兩個(gè)原因規則管理模塊會(huì )在引擎初始化階段將有效版本的規則實(shí)例緩存在本地并且*敏*感*詞*規則變更事件(*敏*感*詞*可以基于 ZooKeeper 實(shí)現)。
預解析規則實(shí)例,因為規則每次解析執行會(huì )導致性能(大對象)問(wèn)題,因此會(huì )在引擎初始化階段解析為運行時(shí)可用的調度棧幀。
規則管理模塊,職責如下,運行時(shí)模塊。分為調度器和 QL 驅動(dòng)器。
嵌入式模式下是基于 MySQL和 Derby 等實(shí)時(shí)性較好的數據庫實(shí)現的。在 Spark 平臺上是基于 Spark SQL 實(shí)現的。
QL 執行器,負責執行結構化查詢(xún)邏輯。兩種不同的運行模式下 QL 執行器在執行 SQL 模式時(shí)會(huì )選擇兩種不同的 QL 執行器實(shí)現,兩種實(shí)現分別是:
Maze 框架能力模型
Maze 框架是一個(gè)適用于非技術(shù)背景人員,支持復雜規則的配置和計算引擎。
規則迭代安全性
規則支持熱部署,系統通過(guò)版本控制,可以灰度一部分流量,增加上線(xiàn)信心。
規則表達能力,框架的表達能力覆蓋絕大部分代碼表達能力。下面用偽代碼的形式展示下 Maze 框架的規則部分具有的能力。
// 輸入N個(gè)FACT對象
function(Fact[] facts) {
// 從FACT對象里提取模式
String xx= facts[0].xx;
// 從某個(gè)數據源獲取特征數據,SQLC數據處理能力遠超sql語(yǔ)言本身能力,SQLC具有編程+SQL的混合能力
List moreFacts = connection.executeQuery("select * from xxx where xx like '%" + xx + "%');
// 對特征數據和FACT對象應用用戶(hù)自定義計算模式
UserDefinedClass userDefinedObj = userDefinedFuntion(facts, moreFacts);
// 使用系統內置表達式模式處理特征
int compareResult = userDefinedObj.getFieldXX().compare(XX);












