规则引擎三

写在前面

以前两篇文章是去年调研和自研规则引擎的存货,今天是最后一篇,后记。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操做。
  • 创建规则分析平台,发现热点优化规则。
  • 扩展因子。
  • 服务注册等。
相关文章
相关标签/搜索