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

54 阅读4分钟

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

本文试图从规则引擎开发的角度切入编译器原理相关内容,介绍编译原理的基本概念。然明确一个规则引擎的设计目标,并完成各部分的设计与实现步骤拆解,最后动手实现了一个规则引擎项目。

认识规则引擎

什么是规则引擎?

规则引擎是一种嵌入在应用服务中的组件,可以将灵活多变的业务决策从服务代码中分离出来。通过使用预定义的语义模块来编写业务逻辑规则。在执行时接受数据输入、解释业务规则,并做出决策。规则引擎能大大提高系统的灵活性和扩展性。

业务规则引擎是在运行时生产环境中执行一个或多个业务规则的软件系统。业务规则系统使这些公司策略和其他操作决策能够独立于应用程序代码进行定义、测试、执行和维护。

通过规则引擎,业务人员便可以通过规则引擎直接修改规则决策,而避免通过开发人员修改,增加工作效率,同时解耦合了工作。

规则引擎的组成

  • 数据输入

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

  • 规则理解

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

  • 规则执行

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

规则引擎的引用场景

在字节跳动,规则引擎已经在风控识别、活动运营、配置下发等场景得到了广泛的应用。开发人员可以将业务逻辑与服务代码解耦,实现灵活、高效的业务策略发布。目前公司内部基于规则引擎的动态决策系统已经承接了千万级别QPS的决策请求。

编译原理基本概念

什么是编译?

编译的过程就是 把某种语言的源程序,在不改变语义的条件下,转换成另一种语言程序(目标语言程序)

词法分析

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

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

如何识别Token? 确定的有限自动机

确定的有限自动机就是一个状态机,它的状态数量是有限的。该状态机在任何一个状态,基于输入的字符,都能做一个确定的状态转换。

语法分析

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

词法分析是识别一个个的单词,而语法分析就是在词法分析的基础上识别出程序的语法结构。这个结构是一个树状结构。这棵树叫做抽象语法树(Abstract Syntax Tree,AST)。树的每个节点(子树)是一个语法单元,这个单元的构成规则就叫“语法”。每个节点还可以有下级节点。

例如,上文中 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 ) 为右操作数。

上下文无关语法

语言句子无需考虑上下文,就可以判断正确性,可使用巴科斯范式来表达:

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(代指基础表达式)可由常量(string, bool, number)或标识符(identifer)组成;mul(代指乘法表达式)可由 primul * pri 组成,以此类推。

递归下降算法

基本思路就是按照语法规则去匹配 Token 串。其通过递归的形式不断地对 Token 进行向下语法展开来构造抽象语法树。

类型检查

  • 类型综合。根据子表示的类型构造出父表达式的类型。例如,表达式 A+B 的类型是根据 A 和 B 的类型定义的。
  • 编译时检查 & 运行时检查。