前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >规则引擎调研与思考(一)

规则引擎调研与思考(一)

作者头像
用户2825413
发布2022-04-19 09:37:28
2K0
发布2022-04-19 09:37:28
举报
文章被收录于专栏:呆呆熊的技术路

1. 规则引擎简述

世界万事万物皆有规则

说起规则引擎, 相信很多小伙伴对于规则引擎产生了很多疑问. 它是什么? 它能做啥? 应该怎么做? 希望通过阅读下面的内容能给你一些启发.

首先规则引擎是什么,我们来看下百度百科是怎么定义的

规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。

上面说的很清晰, 总结一句话规则引擎做的事情就是 录入特定判断逻辑, 通过输入的数据进行决策

规则引擎这么好? 我们的业务适合引入规则引擎吗?

首先我们有个基本的优缺点分析:

规则引擎带来的优点:

  1. 高灵活性 高灵活性带来的直接效果是缩短开发到上线周期, 热更新修复bug
  2. 天生组件化 简化复杂的业务逻辑, 缩减业务代码量, 易于业务逻辑管理.

规则引擎带来的缺点:

  1. 引入了额外服务依赖 对于一些对稳定性、正确性要求极高的场景, 前期不建议引入 (需要提供完善的权限控制和规则单元测试能力)
  2. 前期增加产品、技术学习成本 产品需要具有一定抽象思维, 需求文档中给出系统易变部分进行抽象处理 研发需要学习部分规则语法, 并了解系统实现和约束
  3. 并不能依赖规则热更新满足所有业务判定场景
所以规则引擎并不是万能, 在熟悉规则引擎的具体能力前提下, 根据具体所在的业务场景, 来判断引入后是否可达到效益最高

2. 规则引擎选择

规则引擎/指标

drools

gengine

上手难度

有一定门槛

运行方式

仅支持顺序型

支持顺序/并行/N-M等模式

开发语言

java

golang

社区活跃度

一般

基于本人水平, 此处选择了更易学习的 gengine 作为研究对象 (虽然规则引擎有不同的运行模式和语言, 但对于我们理解本质并没什么区别)

  • gengine是一款基于golang和AST(抽象语法树)开发的规则引擎, gengine支持的语法是一种自定义的DSL
  • gengine于2020年7月由哔哩哔哩(bilibili.com)授权开源
  • gengine现已应用于B站风控系统、流量投放系统、AB测试、推荐平台系统等多个业务场景
  • 你也可以将gengine应用于golang应用的任何需要规则或指标支持的业务场景
gengine 规则引擎使用流程

支持部分规则模式

运行模式

方法名

含义

顺序型

ExecuteSelectedRulesWithControlAndStopTag

按规则优先级执行(从上往下)-耗时为所有规则执行时间累加

并发型

ExecuteSelectedRulesConcurrent

所有规则并发执行(执行时间为执行时间最长的-考虑池限制)

混合模式

ExecuteSelectedRulesMixModel

先执行一个优先级最高的规则,然后并发执行剩下的所有规则

N-M

ExecuteNConcurrentMConcurrent

前N个规则并发执行, M个规则也并发执行/ 前N个规则顺序执行, M个规则并发执行

制定规则中依赖的方法(硬编码)

代码语言:javascript
复制
type ruleResponse struct {
  Code int
  Data map[string]interface{}
}

func success(code int) ruleResponse {
  return ruleResponse{
    Code: code,
    Data: make(map[string]interface{}),
  }
}

func (rule ruleResponse) AddDataParam(name string, value interface{}) {
  rule.Data[name] = value
}

规则中是不支持自定义函数和结构体的, 当需要返回非int、字符串、bool值的时候, 需要我们外部注入对应实现方法, 供规则内调用 (例如自定义结构体、自定义复杂规则验证、获取订单课程等方法)

声明规则

代码语言:javascript
复制
rule1 := `

   rule "case0" "case1测试用例"  salience 0  //后执行
   begin
     Print(flag-2)
     successObj := success(0)
     successObj.AddDataParam("name0","test 000 value")
     return successObj
   end

   rule "case1" "case1测试用例"  salience 1  //后执行
   begin
       a = 8
        if a < 1 {
            println("a < 1")
        } else          if a >= 1 && a <6 {
            println("a >= 1 && a <6")
        } else if a >= 6 && a < 7 {
            println("a >= 6 && a < 7")
        } else if a >= 7 && a < 10 {
            println("a >=7 && a < 10")
        } else        {
            println("a > 10")
        }
   end

   rule "case2" "case2测试用例"  salience 2   //先执行
   begin

     if flag>0 {
       Print(flag+3)
       stag.StopTag = true
       successObj := success(222)
       successObj.AddDataParam("name2","test value")
       return successObj
     }
     
   end
 `

上图中声明了规则, Print、success是我们外部注入的方法, 我们将在下一步制定. rule表示一段新的规则开始 , 第一个为规则名(返回结果时候为对应key), 第二个为描述, 第三个 salience 表示规则的优先级 规则的优先级数字越大优先级越高(当使用 AsGivenSortedName 方法时, 会忽略掉规则内优先级作用 )

注入规则内依赖方法, 初始化规则池

代码语言:javascript
复制
apis := make(map[string]interface{})
apis["success"] = success
apis["Print"] = fmt.Println

var poolMinLen int64 = 50
var poolMaxLen int64 = 100
pool, err := engine.NewGenginePool(poolMinLen, poolMaxLen, engine.SortModel, rule1, apis)

if err != nil {
 fmt.Println(err.Error())
 return
}

第一个参数 poolMinLen 表示初始化池最小50 第二个参数 poolMaxLen 表示初始化池最大100 第三个参数为设置执行模式(分为顺序执行、并发执行等), 只有调用 ExecuteSelectedRules 和 ExecuteSelectedRulesConcurrent 有效 (后续可做规则模式选择) 第四个参数是我们配置的规则 第五个是我们注入的变量值、方法等

调用执行规则

代码语言:javascript
复制
data := make(map[string]interface{})
data["flag"] = 2

stag := &engine.Stag{StopTag: false}
data["stag"] = stag //显示将标记注入进去

//_ = pool.RemoveRules([]string{"case1"}) //移除规则
/*_ = pool.UpdatePooledRulesIncremental(`  //新增规则
      rule "case66" "case66测试用例" salience 3
      begin
         Print("case66")
         return false
      end
   `)*/

_, resp := pool.ExecuteSelectedRulesWithControlAndStopTag(data, true, stag, []string{"case1", "case66", "case2", "case0"})
fmt.Println(resp)
//output:
//5
//map[case2:{222 map[name2:test value]}]

业务调用方变量注入, StopTag 声明为可中断, 执行选定的规则

3. 业务系统应用

层级架构图

image.png

业务场景通过业务编号(标识)调用规则接入层, 根据运行模式运行关联的规则. 接入层负责根据业务编号拿到具体规则进行运行, 同时接入层负责收集业务执行的异常与结果 最底层规则信息管理部分, 负责规则信息数据的维护与整理

业务接入模块分工流程

image.png

一、业务请求侧流程步骤:
  1. 业务方请求 (参数为 业务编号+业务数据) 执行规则
  2. 规则执行获取业务编号对应的规则列表, 根据此业务的运行模式(顺序型、并行等) 执行规则
  3. 执行完成后返回具体执行结果供业务使用
二、规则管理侧能力:
  1. 规则录入、修改、删除
  2. 规则合并创建业务规则包, 生成业务编号供业务使用
  3. 运行模式/规范输入输出

4. 总结

随着业务的快速发展, 代码的生命周期越来越短, 项目慢慢发展成为恐怖的“巨兽”, 吞噬着研发同学的耐心, 叫苦不迭却难以破局, 上面讲了这么多, 我们怎么来判断是否适合引入规则引擎呢?

首先达成共识, 领导及产研同事是认可当下值得去做的, 可以解决掉我们目前发现的痛点 (发现痛点可以先通过 最简单的方式先找出那些类似 if else多分支决策场景, 根据历史改动及业务需求来进行判断), 当然很多时候痛点习惯了就不痛了, 这就需要仔细思考了。

当然最好的话, 你对代码所服务的业务具有很好的预见能力.

参考资料

http://www.bstek.com/#videos

https://rencalo770.github.io/gengine_doc/#/introduce

https://github.com/bilibili/gengine

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小宇技术研究所 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 规则引擎简述
    • 规则引擎这么好? 我们的业务适合引入规则引擎吗?
      • 所以规则引擎并不是万能, 在熟悉规则引擎的具体能力前提下, 根据具体所在的业务场景, 来判断引入后是否可达到效益最高
  • 2. 规则引擎选择
    • gengine 规则引擎使用流程
    • 3. 业务系统应用
      • 一、业务请求侧流程步骤:
        • 二、规则管理侧能力:
        • 4. 总结
        • 参考资料
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档