day11 规则引擎设计与实现(1) | 青训营笔记

89 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 11 天,今天了解规则引擎的组成部分与核心原理.

一. 认识规则引擎

规则引擎的定义

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

规则引擎的优点

  • 解决开发人员重复编码的问题
  • 业务决策与服务本身解耦,提高服务的可维护性缩短开发路径,提高效率

组成部分

  • 数据输入:
    • 支持接受使用预定义的语义编写的规则作为策略集.比如:"price > 500",接受业务的数据作为执行过程中的参数,比如价格,标签等.
  • 规则理解:
    • 能够按照预先定义的词法,语法,优先级,运算符等正确了理解业务理解业务规则所表达的值义
  • 规则执行:
    • 根据执行时输入的参数对策略集中的规则进行正确的解释和执行.同时对规则执行过程中的数据类型进行检查,确保执行结果正确

应用场景

  • 风控对抗 : 与黑灰产对抗过程中,根据黑灰产特征,不断调整和优化对抗策略.
  • 活动策略运营 : 及时根据用户效果反馈进行运营策略的优化和调整.
  • 数据分析和清洗 : 实现不同数据分析,进行快速整理,清洗和转换.

二. 编译原理基本概念

什么是编译

  • 编译的过程就是 把某种语言的源程序,在不改变语义的条件下,转换成另一种语言程序(目标语言程序)
  • 如果源代码编译后要在操作系统上运行,那目标代码就是汇编/机器代码
  • 如果编译后是在虚拟机里执行,那目标代码就可以不是汇编代码,而是一种解释器可以理解的中间形式的代码即可

词法分析

  • 词法分析就是把源代码字符串转换为词法单元(Token)的这个过程。

    例如: price > 500 && (isNewUser || userLevel > 5)
    --> price > 500 && ( isNewUser || userLevel > 5 )

  • 词法单元:由一个词法单元名和一个可选的属性构成

  • 词法单元的大致分类

    • 关键字:每个关键字有一个词法单元
    • 标识符:一个表示所有标识符的词法单元
    • 常量:常量词法单元包含的数字和字符串
    • 运算符:运算法也是一个词法单元,可以是比较运算符,也可以是算术运算符
    • 标点符号:每一个标点符号有一个词法单元。比如括号等等
  • 有限自动机:

    • 确定的有限自动机就是一个状态机,它的状态数量是有限的。该状态机在任何一个状态,基于输入的字符,都能做一个确定的状态转换。
    • image.png
    • 画出一个合理的状态机就可以视为完成一个词法分析

语法分析

  • 词法分析是识别一个个的单词,而语法分析就是在词法分析的基础上识别出程序的语法结构.

    image.png

抽象语法树

  • 语法分析出的结构是一个树状结构。这棵树叫做抽象语法树(Abstract Syntax Tree,AST)。树的每个节点(子树)是一个语法单元,这个单元的构成规则就叫“语法”。每个节点还可以有下级节点.

  • image.png

  • 上下文无关语法

    • 无需考虑上下文,就可以判断正确性,可以使用巴斯克范式(BNF)来表示
      ...
      a := 0
      ...
      
    • BNF是描述上下文无关理论的一种具体方法,通过BNF可以实现上下文无关文法的具体化、公式化、科学化,是实现代码解析的必要条件。
    <expr> ::= <expr> + <term> 
            | <expr> - <term> 
            | <term> 
    <term> ::= <term> * <factor> 
            | <term> / <factor> 
            | <factor> 
    <factor> ::= ( <expr> ) 
            | Num
    

    BNF本质上就是树形分解,分解成一棵抽象语法树

    • 每个产生式就是一个子树,在写编译器时,每个子树对应一个解析函数。
    • 叶子节点叫做 终结符,非叶子节点叫做 非终结符
    • 产生式 : 一个表达式可以由另外已知类型的表达式或者符号推导产生.
      • 内置符号 : 字面量(string,bool,number)标志符,运算符
      • 一个基础表达式可以由常量(string,bool,number)或者标识符(identifier)
      • 一个乘法表达式可以由 基础表达式 或者 乘法表达式 * 基础表达式 组成
      exp : add ; 
      add : (add '+' mul) | mul ; //加法表达式  a + b + c  a + b * c
      mul : (mul '*' pri) | pri ; // 乘法表达式  a * b * c
      pri : string |  bool | number | identifier ; //基础表达式 weight | 20 | "abcde"  
      
  • 递归下降算法

    • 递归下降算法就是自顶向下构造语法树

    • 不断对Token(语法分析)进行语法展开(下降),展开过程可能会遇到递归的情况.

      按照规则
      image.png

      image.png 进行语法展开得到:
      image.png

    • 对于一个非终结符,要从左到右依次匹配其产生式中的每个项,包括非终结符和终结符

    • 在匹配产生式右边的非终结符时,要下降一层,继续匹配该非终结符的产生式。

    • 如果一个语法规则有多个可选的产生式,那么只要有一个产生式匹配成功就行。如果一个产生式匹配不成功,那就回退回来,尝试另一个产生式。这种回退过程,叫做回溯(Backtracking)。

类型检查

  • 类型综合
    • 根据子表达式的类型构造出父表达式的类型.例如:表达式A+B的类型是根据A和B的类型定义的.
  • 编译时检查 & 运行时检查
    类型检查可以发生在表达式的编译阶段,即在构造语法树的阶段,也可以发生在执行时的阶段.
    • 编译时 : 需要提前声明参数的类型,在构建语法树过程中进行类型的检查
      int1 : int  str1 : string
      
    • 执行时 : 可以根据执行时的参数输入的指类型,在执行过程中进行类型检查.
      int1 : 108  str1 : "300"
          ```