規則引擎三

寫在前面

以前兩篇文章是去年調研和自研規則引擎的存貨,今天是最後一篇,後記。json

有人會問,標題不是寫的動手擼嗎?哪裏體現擼了?緩存

其實擼起來一個引擎並不複雜,爲了體現架構思想,調研心得和設計思想反而更重要,相信優秀如你寫代碼沒有任何壓力的。架構

那我就和你們聊聊業務背景和引擎要求。app

設計思想

場景

好比[券表],對於字段屬性有必定的規則要求,好比券的互斥屬性須要作必定的校驗,好比change-config是個json,須要進行解析以後和detail信息作規則校驗,等其餘的一些規則。 梳理出來的須要主要設計到字段屬性的處理,而沒有涉及到複雜的流程,數據問題的處理。異步

核心

  • 定義規則;
  • 肯定規則邊界;

規則

  • 字段規則,涉及到字段長度,某些信息(地理圍欄信息)須要逆向校驗是否準確。
  • 流程規則,須要根據不一樣參數規則進行不一樣分支流程。
  • 變動頻繁,某些業務場景存在每月規則變化的需求。

舉例

  • 字段規則,好比實體字段長度,地理圍欄信息。
  • 流程規則,不一樣來源數據進行不一樣的規則校驗。

校驗

  • 規則:業務實體信息校驗,採用字段校驗規則。
  • 校驗:須要配置對應字段的規則,好比名稱字段長度,地址位置和經緯度是否一致。

方案調研

硬編碼:適用於規則不易變場景。ide

優勢性能

  • 邏輯簡單,易於理解,開發效率高,編碼能夠由編譯器保證。

缺點單元測試

  • 迭代成本高可維護性差,規則變動須要發版,上線週期較長,若是代碼繁雜須要原RD介入。

Drools:開源規則引擎測試

流程:業務分析師編寫業務需求文檔,開發工程師根據規則進行DSL規則編寫,DSL規則入庫,Drools引擎根據規則庫規則進行解析,動態執行規則。優化

優勢

  • 能夠解耦規則創建和規則執行,便於維護。

缺點

  • 須要業務分析師和開發工程師協同工做,缺一不可,存在人效浪費問題。

規則複雜以後,依然存在很差維護問題,某種程度上甚至比硬編碼糟糕。

過多的if,else,when,then不利於維護。

基於Spark數據處理規則引擎

若是場景涉及大部分規則是數據處理,則能夠認爲此場景規則處理等於數據處理。 爲商業分析師提供友好可視化規則界面。 規則引擎將配置信息解析爲Spark做業進行計算。

優勢

  • 規則配置簡單,易上手,支持熱部署。

缺點

  • 使用範圍侷限於數據場景的規則,不能覆蓋更大業務場景。

自研規則引擎

基本假設

n個規則輸入,一個規則結果輸出; 規則支持基本的邏輯運算,算數運算,關係運算,屬性判斷等; 多個原子語義規則之間可聚合,可複用,可拆分;

性能要求

  • 高吞吐;
  • 低延遲;
  • 採用本地緩存加速;
  • 減小遠程進程調用開銷;
  • 提升系統並行度;
  • 代碼採用編譯後或解析後執行;

可用性要求

規則引擎可降級,不影響主流程;

系統設計要求

  • 代碼侵入低,引jar包,採用註解,AOP,threadlocal等方式;
  • 支持熱部署;
  • 支持規則版本回滾,支持規則灰度發佈;

功能要求

  • 下降業務分析師使用門檻;
  • 提供配置中心;
  • 提供規則預警;
  • 結果分析報表展現;
  • 規則執行過程當中有監控,有規則報表分析結果;

設計方案

  • 驗證流程
  • 經過aop攔截須要規則驗證的註解方法;
  • 進行方法入參獲取,獲取參數類名;
  • 若是參數類名和內存中標記的註解Domain名稱相同,則進行規則驗證;
  • 根據類屬性進行對應屬性規則獲取;
  • 進行屬性規則驗證;
  • 驗證不經過則提示對應配置提示信息,並返回;

技術點

  • 註解,反射,AOP,ThreadLocal,Kafka;
  • 規則表達式
  • aviator

難點

  • 規則下發方案,基於RPC推送/基於MQ訂閱/基於Zookeeper的Watcher;
  • 規則編寫的覆蓋能力,不一樣規則對應的不一樣友好提示;

規則解析

示例

賦值

coupon.setId(100L);
coupon.setAcctId(5L);
coupon.setRemark("備註備註備註備註備註1");
coupon.setType((byte) 5);
coupon.setDetail("{\"an\":\"\",\"charge_detail\":[{\"charge_amount\":100000,\"charge_side\":\"1\",\"order_no\":1,\"setAn\":false,\"setCharge_amount\":true,\"setCharge_side\":true,\"setOrder_no\":true},{\"an\":\"17D03004416N001S0009T187R00493A0080$C1CB4\",\"charge_amount\":20000,\"charge_side\":\"14010\",\"order_no\":2,\"setAn\":true,\"setCharge_amount\":true,\"setCharge_side\":true,\"setOrder_no\":true}],\"charge_detailIterator\":[{\"$ref\":\"$.charge_detail[0]\"},{\"$ref\":\"$.charge_detail[1]\"}],\"charge_detailSize\":2,\"charge_method\":0,\"charge_side\":\"1\",\"charge_type\":1,\"default_charge_side\":\"1\",\"default_side_an\":\"\",\"setAn\":true,\"setCharge_detail\":true,\"setCharge_method\":true,\"setCharge_side\":true,\"setCharge_type\":true,\"setDefault_charge_side\":true,\"setDefault_side_an\":true}");
coupon.setCount(120000L);

規則

rule.setKey("id");
rule.setRule("id > 10");
rule.setErrMessage("id 必須大於 10");
rule.setKey("type");
rule.setRule("acctId == 5 && type == 5");
rule.setErrMessage("acctId = 5 type 必須爲5");
rule.setKey("remark");
rule.setRule("string.length(' remark ') > 10"); // 注意表達式都有空格
rule.setErrMessage("remark長度不能小於10");
rule.setKey("detail");
rule.setRule("(<json>detail.charge_detail.(0).charge_amount</json> + <json>detail.charge_detail.(1).charge_amount</json>) == <json>count</json>");
rule.setRuleTypeEnum(CompassRuleTypeEnum.JSON);
rule.setErrMessage("charge_amount值不相等");

Json

{
    "an":"",
    "charge_detail":[
        {
            "charge_amount":100000,
            "charge_side":"1",
            "order_no":1,
            "setAn":false,
            "setCharge_amount":true,
            "setCharge_side":true,
            "setOrder_no":true
        },
        {
            "an":"17D03004416N001S0009T187R00493A0080$C1CB4",
            "charge_amount":20000,
            "charge_side":"14010",
            "order_no":2,
            "setAn":true,
            "setCharge_amount":true,
            "setCharge_side":true,
            "setOrder_no":true
        }
    ],
    "charge_detailIterator":[
        {
            "$ref":"$.charge_detail[0]"
        },
        {
            "$ref":"$.charge_detail[1]"
        }
    ],
    "charge_detailSize":2,
    "charge_method":0,
    "charge_side":"1",
    "charge_type":1,
    "default_charge_side":"1",
    "default_side_an":"",
    "setAn":true,
    "setCharge_detail":true,
    "setCharge_method":true,
    "setCharge_side":true,
    "setCharge_type":true,
    "setDefault_charge_side":true,
    "setDefault_side_an":true
}

收穫

方案和核心代碼現起來比較簡單,可是一個非業務相關項目可能面對着兩個問題,作大和作小。

作小

指的是隻知足當前業務場景和需求,這樣可最快的實現需求,但後續若是有相似的需求不能知足或不易定製,這樣最開始引入這個項目的目的全無。

作大

指的是若是作成一個業務和技術上均可用的項目,達到一個平臺的效果,則須要在研發投入更多的時間。

包括代碼細節,技術方案,UI界面等,後續在系統穩定性方案也須要投入一些時間,這樣作一個非業務相關性的東西投入這麼大是否值得,好比須要大概投入了3,4我的力,完整週期持續了小2個月。

規則引擎有多個場景:風控場景,業務場景。

風控場景

屬於風控產品線產品,總體上功能比較完備,可是對於通常場景顯得重一些,引入了場景,規則,規則因子,appkey,單元測試等不少新的概念,總體上比較重。

業務場景

業務場景比較輕量級一些,對於咱們的場景支持的還能夠,引入上對於代碼入侵能夠接受。

決定採用這種低侵入方案進行對接。

寫在最後

調研收穫

進行調研以前,本身對於業務場景對於規則引擎的需求進行了必定的設計和代碼開發,在考慮上存在一些問題。

通用的方案

調研了一圈,你們你們在實現細節上都很相似,好比基於Aviator的表達式,經過Zk,MQ,DataBus的規則下發,規則放到內存中不存在跨進程調用。

離線方案基於Hive進行分析。

徹底避免代碼侵入 我本身設計的方案上想對代碼作到儘可能少的代碼入侵,甚至零侵入,好比經過AOP的方式實現零侵入。

但通過深度思考後,發現徹底的零侵入會限制規則在程序中的能力,調研了幾個方案以後,發現全部的方案都存在代碼侵入。

獲得的收穫是,不要爲了某種潔癖達到零侵入,適當的代碼侵入更有助於規則引擎的表達,只要作好侵入更友好就能夠。

規則沉澱

經過規則管理平臺配置和修改規則,基於MySql存儲。 規則配置採用多租戶管理,便於不一樣團隊進行操做。

日誌存儲

  • es+hbase,實時日誌
  • hive,離線日誌
  • druid,聚合日誌

監控告警

對於使用流程進行埋點監控,可視化報表查看。

接入方式

  • sdk
  • mq
  • rpc

核心功能

  • 異步規則引擎對接入規則進行並行異步處理。
  • 日誌操做落盤採集,或直接nio上報。
  • 日誌回收採集/分析,日誌規則回放,便於redu操做。
  • 創建規則分析平臺,發現熱點優化規則。
  • 擴展因子。
  • 服務註冊等。
相關文章
相關標籤/搜索