2016年7月恰逢美团点评业务“下半场”,全面优化体验、提升效率、降低成本势在必行方面。技术团队需要做些什么来适应这种变化?这个问题直接影响到以后的工作思路。美团外卖的CRM业务已经进入成熟阶段,规则化的需求几乎撑起了该业务所有需求的半边天。一方面,规则唯一不变的就是“多变”,另一方面,开发团队对“规则制定”的感受是枯燥、疲惫、缺乏技术含量。如何解决规则开发的效率问题,最大限度的解放开发团队成为当前的KPI。规则引擎作为维护策略规则的通用框架很快进入我的脑海。它可以将业务决策逻辑从系统逻辑中分离出来,使两种逻辑可以相互独立地进行更改,可以显着降低两种逻辑的维护成本。如何分析规则引擎的设计是本文的主题,过程中也简单介绍了实现方案。美团规则引擎的应用实践首先回顾一下美团点评的几个业务场景。通过这些场景,大家可以更好的理解规则是什么,规则的边界是什么。在每个场景之后,介绍业务系统目前使用的解决方案和主要优缺点。店铺信息验证场景在合并美团点评之前的美团平台事业部,店铺信息入口作为店铺信息的第一道大门,有一个非常重要的职责,就是质量把控。第一步是针对一些Field验证规则。我们从流程的角度来看一下店铺信息录入业务中验证店铺信息的规则模型(简化版),如下图所示:规则主体包括三部分:分支条件。分支中的逻辑条件是“==”和“<”。简单的计算规则。如:字符串长度。业务自定义计算规则。如:逆地理编码、经纬度反算等。解决方案:硬编码由于历史原因,店铺信息校验采用硬编码的方式。伪代码如下:店铺参数缺少必填项目");}if(fieldA.length()<10){returnResultDOFactory.createResultDO(Code.PARAM_ERROR,"店铺名称长度不能小于10个字符");}if(!isConsistent(fieldB,fieldC,fieldD)){returnResultDOFactory.createResultDO(Code.PARAM_ERROR,"店铺xxx地址、行政区域、经纬度不一致");}优点:当规则较少时,变化不频繁时,开发效率最好。稳定性更好,不会出现语法级别的错误,这是编译系统保证的。缺点:规则迭代成本高,规则的一个小改动需要走全流程(开发、测试、部署)。当存量规则较多时,可维护性较差。规则开发和维护门槛高,规则对业务分析师不可见。业务分析师有规则变更需求后无法自行完成开发,需要开发人员介入开发。店铺审核流程场景流程控制中心(负责在运行时根据输入参数选择不同的流程节点构建流程实例)会根据渠道来源和品牌的特点,决定去哪些节点(不去)进行本次审核在输入的店铺信息中,选择策略的模型如下图所示:计算的是用户输入实体的固定值和属性(如:渠道来源和品牌类型)。解决方案:开源Drools经过从入门到放弃的一系列考察,团队选择基于开源规则引擎Drools来配置流程中审计节点的选择策略。使用Drools后的规则配置流程如下:上图中的DSL为规则体,规则内容如下:rule"1.1"whenpoi:POI(source==1&&brandType==1)thenSystem.out.println("1.1matched");poi.setPassedNodes(1);endrule"1.2"whenpoi:POI(source==1&&brandType==2)thenSystem.out.println("1.2matched");endrule"2.1"whenpoi:POI(source==2&&brandType==1)thenSystem.out.println("2.1matched");poi.setPassedNodes(2);endrule"2.2"whenpoi:POI(source==2&&brandType==2)thenSystem.out.println("2.2matched");poi.setPassedNodes(3);end在实践中,我们发现Drools方案有以下优缺点。由于Drools问题较多,最终放弃了这个方案。优点:策略规则和执行逻辑解耦,便于维护。缺点:业务分析师无法独立完成规则配置。由于规则体DSL是一种编程语言(支持Java、Groovy、Python),所以还是需要开发工程师维护。当规则规模变大时,将变得难以维护,硬编码的优势将不复存在。规则的语法只适用于平面规则。对于嵌套条件语义的规则(嵌套when...then子句inthen),只能在笛卡尔积组合后配置条件,不利于维护。绩效指标计算场景美团外卖业务发展非常迅速,绩效指标规则需要快速迭代才能跟上业务发展的节奏。绩效考核的频率是一个月一次,所以绩效规则的迭代频率也是一个月一次。由于性能规则系统是硬编码的,开发团队需要投入大量人力来满足规则更新需求。2016年10月底,我受绩效团队委托成立项目组,负责开发部署一套绩效指标配置系统。系统的上线直接让产品经理和技术团队的工作量减少了70%。接下来我们先分析一下性能指标计算的规则模型,如下图:规则的主体是结构化数据处理逻辑:规则逻辑是从几个数据源获取数据,然后进行一系列聚合处理(结构化查询SQL语句+少量可用代码实现),最终输出到目标数据源。解决方案:业务定制规则引擎性能规则的主体是数据处理,但我们认为数据处理也属于规则的范畴,所以放在本文中进行分析。下图为性能指标配置系统。trigger负责计时,驱动engine进行计算;视图负责为业务分析人员提供规则配置接口,表达规则的能力取决于视图;引擎负责将配置的规则解析为Spark原语进行计算。优点:规则配置门槛低,视图和引擎内部数据模型完全贴合性能业务模型,业务分析师容易上手。系统支持规则热部署。缺点:适用范围有限,因为视图和引擎的设计完全基于性能业务模型,所以很难低成本修改并推广到其他业务。探索新设计的“CaseStudy”部分,三种落地方案存在的问题总结如下:硬编码迭代成本高。Drools的维护门槛很高。视图对非技术人员并不友好,即使对技术人员来说,维护起来也不比硬编码便宜。性能定制引擎表达能力有限,扩展性差,无法扩展到其他业务。由于“高效的配置规则”是业务中长期存在的刚性需求,而行业内缺乏符合需求的解决方案,2017年2月,我在团队内部成立虚拟团队,负责规则引擎的设计与开发。引擎设计指标是为了覆盖工作中的基本规则迭代需求(包括但不限于“Case”部分的多个场景),并在“Case”部分对现有方案进行扬长避短。以下三个部分将重现本项目的设计过程:“需求模型”,基于“案例”部分的场景,我们将尝试抽象出规则模型,同时提炼出系统设计大纲。“迷宫框架”将根据需求模型设计规则引擎。《Maze框架能力模型》,介绍了Maze框架的特点。对于规则引擎来说,需求模型就是世界的规则。通过“案例”部分的分析,我们对于如何构建规则和规则引擎的思路也逐渐清晰起来。下面两节分别定义了规则引擎的规则数据模型和系统模型,目的是为“迷宫框架”部分的规则引擎产品提供框架指导。规则数据模型规则的本质是一个函数,它由三部分组成:n个输入,1个输出,函数计算逻辑。y=f(x1,x2,…,xn)具体结合“案例”部分的场景,我们梳理出的规则模型如下图所示:主要由三部分组成:FACT对象,规则模型用户输入,作为使用的决策因素。规则,LHS(LeftHandSide)部分是条件分支逻辑。RHS(右侧)部分是执行逻辑。LHS和RHS部分由一种或多种模式组成。模式是规则中的最小单元。模式的输入参数可以是另一个模式或FACT对象(例如,AND[argument1]&&[argument2]中的参数1可以是另一个表达式)。该模式需要支持以下三类:客户端定义的方法、FACT对象的实例方法和静态方法。正则表达式、逻辑运算、算术运算、关系运算、对象属性处理等结构化查询。结果对象,规则处理后的结果。需要支持自定义类型或简单类型(Integer、Long、Float、Double、Short、String、Boolean等)。系统模型我们需要设计一个能够配置、加载、解释和执行上一节数据模型的系统。另外,我们在设计时需要避免“案例”部分三种方案的不足。最后我们定义了如下图所示的系统模型。它主要由三个模块组成:知识库,负责提供配置视图和模式因素。知识库的一个很重要的特点叫做“知识”库,就是知识库可以低成本地扩展知识。知识扩展包括增加视图和模式。视图和模式具有一对一的映射关系。比如我们在界面上显示这样一个视图:大于,小于,等于,那么必然有模式$parameter1>$parameter2与之对应。一方面,降低了操作门槛。一方面限制用户输入,保证输入的合法性。非技术背景(如业务分析师)的视图、人员配置规则以两种方式发挥作用。模式是构成规则的最小单元,不可拆分,可直接由规则引擎执行。资源管理器,负责管理规则。版本管理支持规则迭代更新、回滚、灰度等功能。依赖管理,负责将规则解析成模式树。为了最大化规则的表达能力,每一个模式设计都是非常“原子化”的,所以如果要配置一个语义完整的规则,它必须由多个子规则组成,所以规则之间会存在树状依赖关系。$parameter1+$parameter2>$parameter3这样的规则由多个pattern组成,其依赖关系如下:最终结果/**变量pattern*/||中间结果>$parameter3/**关系运算mode*/||$parameter1+$parameter2/**算术运算模式*/规则引擎负责执行规则。调度器,根据规则的依赖关系和硬件资源驱动模式的执行器执行方式,目标是达到最高的吞吐量或最低的延迟。模式执行器,负责直接执行模式。执行器可以根据业务的表现力需求,基于Drools、Aviator等第三方引擎,甚至可以基于ANTLR进行定制。迷宫框架根据“需求模型”一节中的定义,我们开发了迷宫框架(Maze的意思是迷宫,意思是:像迷宫一样复杂的规则)。Maze框架分为两个引擎:MazeGO(策略引擎)。MazeQ??L(结构化数据处理引擎)。其中MazeGO解析结构化数据处理方式,调用SQLC驱动MazeQ??L完成计算。例如从数据库中查询某BD的月交易量。如果交易量超过30万,执行逻辑A;否则,执行逻辑B。该语义规则需要执行结构化查询。MazeQ??L解析为策略计算模式,调用VectorC驱动MazeGO进行计算。比如有一张订单表,第一列是产品ID,第二列是购买的产品数量,第三列是产品的单价。如果我们需要计算每类商品的总价,则需要对结构化查询结果的每一行进行第二列*第三列等策略模式的计算。术语解释:VectorC是指向量计算,针对矩阵的行和列进行计算。计算方式分为三种:对一行中的多个列进行策略计算。计算是在列上执行的。分组聚合(GroupBy)后,对每个组中的列进行操作。SQLC代表结构化查询,具有执行SQL的能力。MazeGOMazeGO核心主要由三部分组成:Explorer。知识库。MazeGO引擎。另外两个辅助模块是流量控制器和规则效果分析模块。基本结构如下:三个核心模块(引擎、知识库和资源管理器)的职责分别在“需求模型”部分的“系统模型”部分展示。下面仅介绍与“系统模型”不同的部分:MazeGO引擎、预加载规则实例。首先,为了避免在访问规则时需要实时执行远程调用带来的较大延迟,另外,规则是不会一直变化的,所以没有必要每隔一段时间就拉取最新版本您访问的时间。基于以上两点原因,规则管理模块会在本地缓存有效版本的规则实例,并在引擎初始化阶段监控规则变更事件(监控可以基于ZooKeeper实现)。预编译规则实例,因为规则的每次编译和执行都会带来性能问题,所以增量版本的规则会在引擎初始化和规则变更时预编译成可执行代码。规则管理模块。职责如下:流量控制器,负责调度不同版本的规则。方便业务方修改规则,流量的灰度部分会走新规则。规则效果分析。添加或修改规则后,业务方需要分析效果。该模块将提供内部执行路径、运行时参数和规则结果的图像数据,数据可以存储在Hbase上。MazeQ??LMMazeQ??L的核心主要由三部分组成:配置中心。MazeQ??L引擎。平台。MazeQ??L引擎调度器,SQLC和VectorC规则大多由多条规则组成(对于SQLC,依赖规则可以简单理解为子查询),所以也需要和“系统模型”部分一样的调度管理,实现恰好同一水平。QL驱动,驱动平台进行规则计算。因为实际执行任务的平台有多个(将在下一个“平台”部分介绍),所以QL驱动程序也有多个实现。预加载规则实例,首先是为了避免访问规则时需要实时执行远程调用,造成较大的延迟,另外规则不会一直变化,所以不需要拉取每次访问时都是最新版本。基于以上两点原因,规则管理模块会在本地缓存有效版本的规则实例,并在引擎初始化阶段监控规则变更事件(监控可以基于ZooKeeper实现)。预解析规则实例,因为每次规则的解析和执行都会造成性能(大对象)问题,所以会在引擎初始化阶段解析成运行时可用的调度栈帧。规则管理模块具有以下职责,运行时模块。分为调度器和QL驱动。平台负责规则逻辑的实际执行,分为两种运行模式:一种是嵌入在客户端进程中,优点是实时性更好,延迟更低,适用于小批量数据加工;另一种是基于远程模式运行在Spark平台上,适用于离线大规模数据处理。嵌入式模式是基于MySQL、Derby等实时性较好的数据库实现的。它是在Spark平台上基于SparkSQL实现的。QL执行器,负责执行结构化查询逻辑。在两种不同的运行模式下,QL执行器在执行SQL模式时会选择两种不同的QL执行器实现。两种实现方式是:配置中心提供规则配置视图。版本管理,同“系统模型”部分。数据源绑定就是定义参与计算的SQL逻辑使用的数据源,方便系统管理。结构查询定义就是定义SQL规则,是主要规则的内容。向量计算定义,定义VectorC类计算(VectorC见《迷宫框架》一章开头的介绍)。MazeFramework能力模型MazeFramework是一个适合非技术人员使用的支持复杂规则的配置和计算引擎。规则迭代安全规则支持热部署,系统可以通过版本控制对部分流量进行灰度化,增加上线信心。规则表达能力,框架的表达能力涵盖了大部分的代码表达能力。下面以伪代码的形式展示了迷宫框架规则部分的能力。//输入N个FACT对象function(Fact[]facts){//从FACT对象中提取模式Stringxx=facts[0].xx;//从一个数据源中获取特征数据,SQLC的数据处理能力远超SQL的能力语言本身,SQLC具有编程的混合能力+SQLList
