2016 年 7 月恰逢美團點評的業務進入「下半場」,須要在各個環節優化體驗、提高效率、下降成本。技術團隊須要怎麼作來適應這個變化?這個問題直接影響着以後的工做思路。react
美團外賣的 CRM 業務步入成熟期,規則類需求幾乎撐起了這個業務全部需求的半邊天。sql
一方面規則惟一不變的是「多變」,另外一方面開發團隊對「規則開發」的感覺是乏味、疲憊和缺少技術含量。如何解決規則開發的效率問題,最大化解放開發團隊成爲目前的一個 KPI。數據庫
規則引擎做爲常見的維護策略規則的框架很快進入個人思路。它能將業務決策邏輯從系統邏輯中抽離出來,使兩種邏輯能夠獨立於彼此而變化,這樣能夠明顯下降兩種邏輯的維護成本。編程
分析規則引擎如何設計正是本文的主題,過程當中也簡單介紹了實現方案。緩存
美團規則引擎應用實踐安全
首先回顧幾個美團點評的業務場景,經過這些場景你們能更好地理解什麼是規則,規則的邊界是什麼。併發
在每一個場景後面都介紹了業務系統如今使用的解決方案以及主要的優缺點。框架
門店信息校驗編程語言
場景ide
美團點評合併前的美團平臺事業部中,門店信息入口做爲門店信息的第一道關卡,有一個很重要的職責,就是質量控制,其中第一步就是針對一些字段的校驗規則。
下面從流程的角度看下門店信息入口業務裏校驗門店信息的規則模型(已簡化),以下圖:
規則主體包括三部分:
分支條件。分支內邏輯條件爲「==」和「<」。 簡單計算規則。如:字符串長度。 業務定製計算規則。如:逆地址解析、經緯度反算等。方案:硬編碼
因爲歷史緣由,門店信息校驗採用了硬編碼的方式,僞代碼以下:
if (StringUtil.isBlank(fieldA) || StringUtil.isBlank(fieldB) || StringUtil.isBlank(fieldC) || StringUtil.isBlank(fieldD)) { return ResultDOFactory.createResultDO(Code.PARAM_ERROR, "門店參數缺乏必填項"); }if (fieldA.length() < 10) { return ResultDOFactory.createResultDO(Code.PARAM_ERROR, "門店名稱長度不能少於10個字符"); } if (!isConsistent(fieldB, fieldC, fieldD)) { return ResultDOFactory.createResultDO(Code.PARAM_ERROR, "門店xxx地址、行政區和經緯度不一致"); }優勢:
當規則較少、變更不頻繁時,開發效率最高。 穩定性較佳,語法級別錯誤不會出現,由編譯系統保證。缺點:
規則迭代成本高,對規則的少許改動就須要走全流程(開發、測試、部署)。 當存量規則較多時,可維護性差。 規則開發和維護門檻高,規則對業務分析人員不可見。業務分析人員有規則變動需求後沒法自助完成開發,須要由開發人員介入開發。門店審覈流程
場景
流程控制中心(負責在運行時根據輸入參數選擇不一樣的流程節點從而構建一個流程實例)會根據輸入門店信息中的渠道來源和品牌等特徵肯定本次審覈(不)走哪些節點,其中選擇策略的模型以下圖:
規則主體是分支條件:
分支條件主體是「==」,參與計算的參數是固定值和用戶輸入實體的屬性(好比:渠道來源和品牌類型)。方案:開源 Drools 從入門到放棄
通過一系列調研,團隊選擇基於開源規則引擎 Drools 來配置流程中審覈節點的選擇策略。使用 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在實踐中,咱們發現 Drools 方案有以下幾個優缺點,因爲 Drools 的問題較多,最後這個方案仍是放棄了。
優勢:
策略規則和執行邏輯解耦方便維護。缺點:
業務分析師沒法獨立完成規則配置,因爲規則主體 DSL 是編程語言(支持 Java,Groovy,Python),所以仍然須要開發工程師維護。 規則規模變大之後也會變得很差維護,相對硬編碼的優點便不復存在。 規則的語法僅適合扁平的規則,對於嵌套條件語義(then 裏嵌套 when...then 子句)的規則只能將條件進行笛卡爾積組合之後進行配置,不利於維護。績效指標計算
場景
美團外賣業務發展很是迅速,績效指標規則須要快速迭代才能緊跟業務發展步伐。績效考覈頻率是一個月一次,所以績效規則的迭代頻率也是每個月一次。由於績效規則系統是硬編碼實現,所以開發團隊須要投入大量的人力知足規則更新需求。
2016 年 10 月底,我受績效團隊委託成立一個項目組,開發部署了一套績效指標配置系統,系統上線直接減小了產品經理和技術團隊 70% 的工做量。
下面咱們首先分析下績效指標計算的規則模型,以下圖:
規則主體是結構化數據處理邏輯:
規則邏輯是從若干數據源獲取數據,而後進行一系列聚合處理(能夠採用結構化查詢 SQL 語句+少許代碼實現),最後輸出到目標數據源。
方案:業務定製規則引擎
績效規則主體是數據處理,但咱們認爲數據處理一樣屬於規則的範疇,所以咱們將其放在本文進行分析。
下圖是績效指標配置系統,觸發器負責定時驅動引擎進行計算;視圖負責給商業分析師提供規則配置界面,規則表達能力取決於視圖;引擎負責將配置的規則解析成 Spark 原語進行計算。
優勢:
規則配置門檻低,視圖和引擎內部數據模型徹底貼合績效業務模型,所以業務分析師很容易上手。 系統支持規則熱部署。缺點:
適用範圍有限,由於視圖和引擎的設計徹底基於績效業務模型,所以很難低成本修改後推廣到別的業務。探索全新設計
「案例」一節中三種落地方案的問題總結以下:
硬編碼迭代成本高。 Drools 維護門檻高。視圖對非技術人員不友好,即便對於技術人員來講維護成本也不比硬編碼低。 績效定製引擎表達能力有限且擴展性差,沒法推廣到別的業務。因爲「高效配置規則」是業務里長期存在的剛需,且行業內又缺少符合需求的解決方案,2017 年 2 月我在團隊內部設立了一個虛擬小組專門負責規則引擎的設計研發。
引擎設計指標是要覆蓋工做中基礎的規則迭代需求(包括但不限於「案例」一節中的多個場景),同時針對「案例」一節中已有解決方案揚長避短。
下面分三節來重現這個項目的設計過程:
「需求模型」,會基於「案例」一節的場景嘗試抽象出規則模型,同時提煉出系統設計大綱。 「Maze 框架」,會基於需求模型設計一個規則引擎。 「Maze 框架能力模型」,會介紹 Maze 框架的特色。需求模型
對規則引擎來講,世界皆規則。經過「案例」一節的分析,咱們對規則以及規則引擎該如何構建的思路正逐漸變得清晰。
下面兩節分別定義規則數據模型和規則引擎的系統模型,目標是對「Maze 框架」一節中的規則引擎產品進行框架性指導。
規則數據模型
規則本質是一個函數,由 n 個輸入、1 個輸出和函數計算邏輯 3 部分組成。
y = f(x1, x2, …, xn)
具體結合「案例」一節中的場景,咱們梳理出的規則模型以下圖所示:
主要由三部分構成:
FACT 對象,用戶輸入的規則模型,做爲決策因子使用。 規則,LHS(Left Hand Side)部分即條件分支邏輯。RHS(Right Hand Side)部分即執行邏輯。LHS 和 RHS 部分是由一個或多個模式構成的。模式是規則內最小單位。模式的輸入參數能夠是另外一個模式或 FACT 對象(好比邏輯與運算[參數 1] && [參數 2]中參數 1 能夠是另外一個表達式)。
模式須要支持如下三種類別:
客戶定義方法,FACT 對象的實例方法、靜態方法。 常規表達式,邏輯運算、算數運算、關係運算、對象屬性處理等。 結構化查詢。結果對象,規則處理完畢後的結果。須要支持自定義類型或者簡單類型(Integer、Long、Float、Double、Short、String、Boolean 等)。
系統模型
咱們須要設計一個系統能配置、加載、解釋執行上節中的數據模型,另外設計時還須要規避「案例」一節 3 個方案的缺點。最終咱們定義了以下圖所示的系統模型。
主要由三個模塊構成:
知識庫,負責提供配置視圖和模式因子。知識庫之因此叫「知識」庫一個很重要的特徵是知識庫能夠低成本擴展知識。知識擴展包括視圖和模式的添加,視圖和模式有一對一映射關係,好比咱們在界面上展現一個如:大於小於等於同樣的視圖,則必定有一個模式$參數 1 > $參數 2與之對應。
一方面下降操做門檻。 一方面約束用戶輸入,保證輸入合法性。 視圖,用於業務分析師等非技術背景的人員配置規則,做用於兩方面。 模式,構成規則的最小單位,不可拆分,能夠直接被規則引擎執行。 資源管理器,負責管理規則。 版本管理,支持規則迭代更新、回滾和灰度等功能。 依賴管理,負責將規則解析爲模式樹。爲了最大限度地加強規則的表達能力,每個模式設計都很「原子」,這樣若是想配置一個完整語義的規則,則必須由多個子規則共同構成,所以規則之間會有樹形依賴關係。如$參數 1 + $參數 2 > $參數 3 這樣的規則即是由多個模式「複合」而成,則他的依賴關係以下所示:
最終結果 /** 變量模式 */ | | 中間結果 > $參數3 /** 關係運算模式 */ | | $參數1 + $參數2 /** 算數運算模式 */ 規則引擎,負責執行規則。 調度器,根據規則的依賴關係以及硬件資源驅動模式執行器執行模式,目標是達到最大吞吐或最低延遲。 模式執行器,負責直接執行模式。執行器能夠根據業務的表達能力需求選擇基於 Drools、Aviator 等第三方引擎,甚至能夠基於 ANTLR 定製。Maze 框架
基於"需求模型"一節的定義,咱們開發了 Maze 框架(Maze 是迷宮的意思,寓意:迷宮同樣複雜的規則)。
Maze 框架分兩個引擎:
MazeGO(策略引擎)。 MazeQL(結構化數據處理引擎)。其中 MazeGO 內解析到結構化數據處理模式會調用 SQLC 驅動 MazeQL 完成計算,好比:從數據庫裏查詢某個 BD 的月交易額,若是交易額超過 30 萬則執行 A 邏輯不然執行 B 邏輯,這個語義的規則須要執行結構化查詢。
MazeQL 內解析到策略計算模式會調用 VectorC 驅動 MazeGO 進行計算,好比:有一張訂單表,其中第一列是商品 ID,第二列是商品購買數量,第三列是此商品的單價。
咱們須要計算每類商品的總價則須要對結構化查詢到的結果的每一行執行第二列 * 第三列這樣的策略模式計算。
名詞解釋:
VectorC 指向量計算,針對矩陣的行列進行計算。有三種計算方式: 針對一行的多列進行策略計算。 針對一列進行計算。 針對分組聚合(GroupBy)後的每一組內的列進行運算。 SQLC 指結構化查詢,擁有執行 SQL 的能力。MazeGO
MazeGO 核心主要由三部分構成:
資源管理器。 知識庫。 MazeGO 引擎。另外兩個輔助模塊是流量控制器和規則效果分析模塊,基本構成以下圖:
三個核心模塊(引擎、知識庫和資源管理器)的職責見「需求模型」一節中「系統模型」一節。
下面只介紹下和「系統模型」不一樣的部分:
MazeGO引擎,預加載規則實例。首先爲了不訪問規則時須要實時執行遠程調用而形成較大的時延,另外規則並非時刻發生變動沒有必要每次訪問時拉取一次最新版本。基於以上兩個緣由規則管理模塊會在引擎初始化階段將有效版本的規則實例緩存在本地而且監聽規則變動事件(監聽能夠基於 ZooKeeper 實現)。
預編譯規則實例,由於規則每次編譯執行會致使性能問題,所以會在引擎初始化和規則有變動這兩個時機將增量版本的規則預編譯成可執行代碼。規則管理模塊。職責以下:
流量控制器,負責不一樣版本規則的調度。方便業務方修改規則後,灰度部分流量到新規則。 規則效果分析,規則新增或修改後,業務方須要分析效果。本模塊會提供規則內部執行路徑、運行時參數和結果的鏡像數據,數據能夠存儲在 Hbase 上。MazeQL
MazeQL 核心主要由三部分構成:
配置中心。 MazeQL 引擎。 平臺。 MazeQL引擎調度器,SQLC 和 VectorC 類規則大多由多個規則組合而成(對於 SQLC 而言能夠將依賴的規則簡單的理解爲子查詢),所以也須要和「系統模型」一節同樣的調度管理,實現層面徹底一致。
QL 驅動器,驅動平臺進行規則計算。由於任務的實際執行平臺有多種(會在下一個「平臺」部分介紹),所以 QL 驅動器也有多種實現。
預加載規則實例,首先爲了不訪問規則時須要實時執行遠程調用而形成較大的時延,另外規則並非時刻發生變動沒有必要每次訪問時拉取一次最新版本。
基於以上兩個緣由規則管理模塊會在引擎初始化階段將有效版本的規則實例緩存在本地而且監聽規則變動事件(監聽能夠基於 ZooKeeper 實現)。
預解析規則實例,由於規則每次解析執行會致使性能(大對象)問題,所以會在引擎初始化階段解析爲運行時可用的調度棧幀。
規則管理模塊,職責以下,運行時模塊。分爲調度器和 QL 驅動器。
ƽ̨負責實際執行規則邏輯,分兩種運行模式:一種是以嵌入式方式運行在客戶端進程內部,好處是實時性更好,時延更低,適合小批量數據處理;另外一種是以遠程方式運行在 Spark 平臺,適合離線大規模數據處理。
嵌入式模式下是基於 MySQL 和 Derby 等實時性較好的數據庫實現的。在 Spark 平臺上是基於 Spark SQL 實現的。
QL 執行器,負責執行結構化查詢邏輯。兩種不一樣的運行模式下 QL 執行器在執行 SQL 模式時會選擇兩種不一樣的 QL 執行器實現,兩種實現分別是:
配置中心提供規則配置視圖。版本管理,同「系統模型」一節。
數據源綁定,便是定義參與計算的 SQL 邏輯中使用到的數據源,便於系統進行管理。
結構查詢定義,便是定義 SQL 規則,這是主體規則內容。
向量計算定義,定義 VectorC 類計算(VectorC見「Maze框架」章節開頭的介紹)。
Maze 框架能力模型
Maze 框架是一個適用於非技術背景人員,支持複雜規則的配置和計算引擎。
規則迭代安全性
規則支持熱部署,系統經過版本控制,能夠灰度一部分流量,增長上線信心。
規則表達能力,框架的表達能力覆蓋絕大部分代碼表達能力。下面用僞代碼的形式展現下 Maze 框架的規則部分具備的能力。
// 輸入N個FACT對象 function(Fact[] facts) { // 從FACT對象裏提取模式 String xx= facts[0].xx; // 從某個數據源獲取特徵數據,SQLC數據處理能力遠超sql語言自己能力,SQLC具備編程+SQL的混合能力 List<Fact> moreFacts = connection.executeQuery("select * from xxx where xx like '%" + xx + "%'); // 對特徵數據和FACT對象應用用戶自定義計算模式 UserDefinedClass userDefinedObj = userDefinedFuntion(facts, moreFacts); // 使用系統內置表達式模式處理特徵 int compareResult = userDefinedObj.getFieldXX().compare(XX); // 聲明用戶自定義對象 UserDefinedResultClass userDefinedResultObj = new UserDefinedResultClass(); // 使用系統內置條件語句模式處理特徵 if (compareResult == 0) { userDefinedResultObj.setCompareResult(Boolean.FALSE); } else if (compareResult > 0) { userDefinedResultObj.setCompareResult(Boolean.FALSE); } else { userDefinedResultObj.setCompareResult(Boolean.TRUE); } // 將結果返回給客戶 return userDefinedResultObj; }規則執行效率
執行效率分三方面:
引擎的調度模塊會確保吞吐優先,而且調度併發度等系統配置能夠根據資源狀況調整。 引擎運行過程當中沒有遠程通訊開銷。 引擎執行代碼實現編譯或解析後執行,運行效率較高。規則接入成本
開發人員接入:
首先,開發人員在項目工程裏導入一個 MazeGO jar 包。 而後,開發人員在項目工程裏須要調用計算規則的地方引入 MazeGO client(以下代碼片斷)。 // 初始化MazeGO client,建議在本應用程序的初始化階段執行 MazeGOReactor reactor = new MazeGOReactor(); reactor.setMazeIds(Arrays.asList(<mazeId>)); reactor.init(); // 調用MazeGO client執行規則 reactor.go(<mazeId>, <fact>); // 銷燬MazeGO client,建議在本應用程序的銷燬階段執行 reactor.destroy();規則配置,規則配置基本實現由業務分析師、產品經理或運營人員自助完成。
業務分析師在 MazeGO 上配置規則的視圖以下圖所示:
總結
本文開頭介紹了幾個工做中的規則使用場景,順帶引出了多個不一樣的解決方案,最後介紹了 Maze 框架的設計,基本上展示了咱們對這個框架思考和設計的整個過程。