规则引擎设计与实现 | 青训营笔记

107 阅读5分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 6 天

前言

这次我们一起来了解规则引擎的组成部分和应用场景,学习并掌握规则引擎的设计与实现原理,明确一个规则引擎的设计目标,并完成各部分的设计与实现步骤拆解,动手实现一个规则引擎项目。让我们一起进入学习吧!

认识规则引擎

规则引擎的定义

规则引擎是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。接受数据输入,解释业务规则,并根据业务规则做出业务决策。这样,通过规则引擎,我们就可以解决开发人员重复编码的问题,使业务决策与服务本身进行解耦,提高了服务的可维护性,缩短了开发路径,提高开发效率。

规则引擎的组成部分

规则引擎由三部分组成:

  • 数据输入:支持接收使用预定义的语义编写的规则作为策略集。比如,price > 500;接受业务的数据作为执行过程中的参数,比如价格、标签等。

  • 规则理解:能够按照预先定义的词法、语法、优先级、运算符等正确理解业务规则所表达的语义。

  • 规则执行:根据执行时输入的参数对策略集中的规则进行正确的解释和执行。同时对规则执行过程中的数据类型进行检查,确保执行结果正确。

应用场景

  • 风控对抗
  • 活动策略运营
  • 数据分析和清洗

编译原理基本概念

词法分析 Lexical Analysis

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

例如,price > 500 && (isNewUser || userLevel > 5) 这段表达式可被分割为 price>500&&(isNewUser||userLevel>5) 几部分(Token)。

我们可以通过有限自动机(Finite-State Automaton)  来对 Token 进行识别。有限自动机就是一个状态机,他的状态数量是有限的,该状态机在任何一个状态,基于输入的字符,都能做一个确定的状态转换。

确定的有限自动机 DFA | Deterministic Finite Automaton, image.png

语法分析 Syntax Analysis

语法分析就是在词法分析的基础上,识别表达式的语法结构的过程。

例如,上文中 price, >, 500, &&, (, isNewUser, ||, userLevel, >, 5, ) 可被识别为如下几部分:

  • price > 500,其中 > 为操作符,price 为左操作数,500 为 右操作数;
  • isNewUser || userLevel > 5,其中 || 为操作符,isNewUser 为左操作数,userLevel > 5 为右操作数;
  • userLevel > 5,其中 > 为操作符,userLevel 为左操作数,5 为右操作数;
  • price > 500 && ( isNewUser || userLevel > 5 ),其中 && 为操作符,price > 500 为左操作数,( isNewUser || userLevel > 5 ) 为右操作数。

抽象语法树 Abstract Syntax Tree

表达式的语法结构可以用树状结构来表示。这棵树就是抽象语法树(Abstract Syntax Tree,AST)。树的每个节点(子树)是一个语法单元,这个单元的构成规则就叫“语法”。每个节点还可以有下级节点。

image.png

上下无关语法 Context-Free Grammar

语言句子无需考虑上下,就可以判断正确性。

巴克斯范式(BNF)

BNF是描述上下文无关理论的一种具体方法,通过BNF可以实现上下文无关文法的具体化、公式化、科学化,是实现代码解析的必要条件。

exp : add;
add : add '+' mul | mul; // 加法表达式,可匹配 a + b + c 或 a + b * c
mul : mul '*' pri | pri; // 乘法表达式,可匹配 a * b * c
pri : string | bool | number | identifer; // 基础表达式,可匹配 weight 或 20 或 "abcde"

上面的每一行代码被称为产生式。产生式是指一种可以由另外已知类型的表达式或者符号推导产生的表达式。

上例中,一个pri(基础表达式)可由常量(stringboolnumber)或标识符(identifer)组成;一个mul(乘法表达式)可由 pri(基础表达式) 或 mul (乘法表达式)* pri (基础表达式)组成。

递归下降算法 Recursive Descent Parsing

递归下降算法就是自顶向下构造语法树,不断地对Token进行语法展开(下降),展开过程中可能会遇到递归的情况。

类型检查

  • 类型综合。根据子表示的类型构造出父表达式的类型。例如,表达式 A+B 的类型是根据 A 和 B 的类型定义的。

image.png

  • 编译时检查 & 运行时检查。

image.png

设计一个规则引擎

使用 Hertz 框架开发一个 HTTP 服务,并使用 MySQL 存储表达式和参数。在该服务中,需要实现以下接口:

  1. 直接表达式执行接口,可以实时编译并执行一个表达式,并返回编译结果。
  2. 新增表达式接口,可以新增一条表达式到数据库中,并返回表达式在数据库中的 ID。在新增表达式之前需要检测该表达式是否已经存在,如果已经存在,则直接返回该表达式的 ID。同时需要检测表达式是否合法,如果编译失败,则返回错误码和编译错误。
  3. 查询表达式接口,查询数据库中所有的表达式。
  4. 删除表达式接口,根据 ID 删除表达式。如果表达式不存在,则返回错误码和错误信息。如果删除成功,则返回被删除的表达式信息。

引用

  • 第五届字节跳动青训营-「【实践课】规则引擎设计与实现」