网易考拉规则引擎平台架构设计与实践

此文已由做者肖凡受权网易云社区发布。
html

欢迎访问网易云社区,了解更多网易技术产品运营经验。nginx

背景

考拉安所有技术这块目前主要负责两块业务:一个是内审,主要是经过敏感日志管理平台搜集考拉全部后台系统的操做日志,数据导入到es后,结合storm进行实时计算,主要有行为查询、数据监控、事件追溯、风险大盘等功能;一个是业务风控,主要是下单、支付、优惠券、红包、签到等行为的风险控制,对抗的风险行为包括黄牛刷单、恶意占用库存、机器领券、撸羊毛等。这两块业务其实有一个共通点,就是有大量须要进行规则决策的场景,好比内审中须要进行实时监控,当同一我的在一天时间内的导出操做超过多少次后进行告警,当登陆时不是经常使用地登陆而且设备指纹不是该帐号使用过的设备指纹时告警。而在业务风控中须要使用到规则决策的场景更多,因为涉及规则的保密性,这里就不展开了。总之,基于这个出发点,安所有决定开发出一个通用的规则引擎平台,来知足以上场景。redis


写在前面

在给出总体架构前,想跟你们聊聊关于架构的一些想法。目前架构上的分层设计思想已经深刻人心,你们都知道要分红controller,server,dao等,是由于咱们刚接触到编码的时候,mvc的模型已经大行其道,早期的jsp里面包含大量业务代码逻辑的方式已经基本绝迹。可是这并非一种面向对象的思考方式,而每每咱们是以一种面向过程的思惟去编程。举个简单例子,咱们要实现一个网银帐户之间转帐的需求,每每会是下面这种实现方式:算法


  1. 设计一个帐户交易服务接口AccountingService,设计一个服务方法transfer(),并提供一个具体实现类AccountingServiceImpl,全部帐户交易业务的业务逻辑都置于该服务类中。spring

  2. 提供一个AccountInfo和一个Account,前者是一个用于与展现层交换帐户数据的帐户数据传输对象,后者是一个帐户实体(至关于一个EntityBean),这两个对象都是普通的JavaBean,具备相关属性和简单的get/set方法。sql

  3. 而后在transfer方法中,首先获取A帐户的余额,判断是否大于转帐的金额,若是大于则扣减A帐户的余额,并增长对应的金额到B帐户。编程


这种设计在需求简单的状况下看上去没啥问题,可是当需求变得复杂后,会致使代码变得愈来愈难以维护,整个架构也会变的腐烂。好比如今须要增长帐户的信用等级,不一样等级的帐户每笔转帐的最大金额不一样,那么咱们就须要在service里面加上这个逻辑。后来又须要记录转帐明细,咱们又须要在service里面增长相应的代码逻辑。最后service代码会因为需求的不断变化变得愈来愈长,最终变成别人眼中的“祖传代码”。致使这个问题的根源,我认为就是咱们使用的是一种面向过程的编程思想。那么如何去解决这种问题呢?主要仍是思惟方式上须要改变,咱们须要一种真正的面向对象的思惟方式。好比一个“人”,除了有id、姓名、性别这些属性外,还应该有“走路”、“吃饭”等这些行为,这些行为是自然属于“人”这个实体的,而咱们定义的bean都是一种“失血模型”,只有get/set等简单方法,全部的行为逻辑所有上升到了service层,这就致使了service层过于臃肿,而且很难复用已有的逻辑,最后造成了各个service之间错综复杂的关联关系,在作服务拆分的时候,很难划清业务边界,致使服务化进程陷入泥潭。缓存


对应上面的问题,咱们能够在Account这个实体中加入本应该就属于这个实体的行为,好比借记、贷记、转帐等。每一笔转帐都对应着一笔交易明细,咱们根据交易明细能够计算出帐户的余额,这个是一个潜在的业务规则,这种业务规则都须要交由实体自己来维护。另外新增帐户信用实体,提供帐户单笔转帐的最大金额计算逻辑。这样咱们就把本来所有在service里面的逻辑划入到不一样的负责相关职责的“领域对象”当中了,service的逻辑变得很是清楚明了,想实现A给B转帐,直接获取A实体,而后调用A实体中的转帐方法便可。service将再也不关注转帐的细节,只负责将相关的实体组织起来,完成复杂的业务逻辑处理。安全


上面的这种架构设计方式,其实就是一种典型的“领域驱动设计(DDD)”思想,在这里就不展开说明了(主要是本身理解的还不够深刻,怕误导你们了)。DDD也是目前很是热门的一种架构设计思想了,它不能减小你的代码量,可是能使你的代码具备很高的内聚性,当你的项目变得愈来愈复杂时,能保持架构的稳定而不至于过快的腐烂掉,不了解的同窗能够查看相关资料。要说明的是,没有一种架构设计是万能的、是能解决全部问题的,咱们须要作的是吸取好的架构设计思惟方式,真正架构落地时仍是须要根据实际状况选择合适的架构。架构


总体架构设计

上面说了些架构设计方面的想法,如今咱们回到规则引擎平台自己。咱们抽象出了四个分层,从上到下分别为:服务层、引擎层、计算层和存储层,整个逻辑层架构见下图:


Alt pic


  • 服务层:服务层主要是对外提供服务的入口层,提供的服务包括数据分析、风险检测、业务决策等,全部的服务所有都是经过数据接入模块接入数据,具体后面讲

  • 引擎层:引擎层是整个平台的核心,主要包括了执行规则的规则引擎、还原事件现场和聚合查询分析的查询引擎以及模型预测的模型引擎

  • 计算层:计算层主要包括了指标计算模块和模型训练模块。指标会在规则引擎中配置规则时使用到,而模型训练则是为模型预测作准备

  • 存储层:存储层包括了指标计算结果的存储、事件信息详情的存储以及模型样本、模型文件的存储


在各个分层的逻辑架构划定后,咱们就能够开始分析整个平台的业务功能模块。主要包括了事件接入模块、指标计算模块、规则引擎模块、运营中心模块,整个业务架构以下图:


Alt pic


1.事件接入中心


事件接入中心主要包括事件接入模块和数据管理模块。数据接入模块是整个规则引擎的数据流入口,全部的业务方都是经过这个模块接入到平台中来。提供了实时(dubbo)、准实时(kafka)和离线(hive)三种数据接入方式。数据管理模块主要是进行事件的元数据管理、标准化接入数据、补全必要的字段,以下图: Alt pic


2.指标计算模块


指标计算模块主要是进行指标计算。一个指标由主维度、从维度、时间窗口等组成,其中主维度至少有一个,从维度最多有一个。以下图: Alt pic


举个例子,如有这样一个指标:“最近10分钟,同一个帐号在同一个商家的下单金额”,那么主维度就是下单帐号+商家id,从维度就是订单金额。能够看到,这里的主维度至关于sql里面的group by,从维度至关于count,数值累加至关于sum。从关于指标计算,有几点说明下:


  1. key的构成。咱们的指标存储是用的redis,那么这里会涉及到一个key该如何构建的问题。咱们目前的作法是:key=指标id+版本号+主维度值+时间间隔序号。

    • 指标id就是指标的惟一标示;

    • 版本号是指标对象的版本,每次更新完指标都会更新对应的版本号,这样可让就的指标一次所有失效;

    • 主维度值是指当前事件对象中,主维度字段对应的值,好比一个下单事件,主维度是用户帐号,那么这里就是对应的相似XXX@163.com,若是有多个主维度则须要所有组装上去;

    • 若是主维度的值出现中文,这样直接拼接在key里面会有问题,能够采用转义或者md5的方式进行。

    • 时间间隔序号是指当前时间减去指标最后更新时间,获得的差值再除以采样周期,获得一个序号。这么作主要是为了实现指标的滑动窗口计算,下面会讲

  2. 滑动窗口计算。好比咱们的指标是最近10分钟的同一用户的下单量,那么咱们就须要实现一种相似的滑动窗口算法,以便任什么时候候都能拿到“最近10分钟”的数据。这里咱们采用的是一种简单的算法:建立指标时,指定好采样次数。好比要获取“最近10分钟”的数据,采样次数设置成30次,这样咱们会把每隔20秒的数据会放入一个key里面。每次一个下单事件过来时,计算出时间间隔序号(见第1点),而后组装好key以后看该key是否存在,存在则进行累计,不然往redis中添加该key。

  3. 如何批量获取key。每次获取指标值时,咱们都是先计算出须要的key集合(好比我要获取“单个帐号最近10分钟的下单量”,我可能须要获取30个key,由于每一个key的跨度是20s),而后获取到对应的value集合,再进行累加。而实际上咱们只是须要累加后的值,这里能够经过redis+lua脚本进行优化,脚本里面直接根据key集合获取value后进行累加而后返回给客户端,这样就较少了每次响应的数据量。

  4. 如何保证指标的计算结果不丢失?目前的指标是存储在redis里面的,后来会切到solo-ldb,ldb提供了持久化的存储引擎,能够保证数据不丢失。


3.规则引擎模块

计划开始作规则引擎时进行过调研,发现不少相似的平台都会使用drools。而咱们从一开始就放弃了drools而所有使用groovy脚本实现,主要是有如下几点考虑:


  • drools相对来讲有点重,并且它的规则语言无论对于开发仍是运营来讲都有学习成本

  • drools使用起来没有groovy脚本灵活。groovy能够和spring完美结合,而且能够自定义各类组件实现插件化开发。

  • 当规则集变得复杂起来时,使用drools管理起来有点力不从心。


固然还有另一种方式是将drools和groovy结合起来,综合双方的优势,也是一种不错的选择,你们能够尝试一下。


规则引擎模块是整个平台的核心,咱们将整个模块分红了如下几个部分: Alt pic


规则引擎在设计中也碰到了一些问题,这里给你们分享下一些心得:


  • 使用插件的方式加载各类组件到上下文中,极大的方便了功能开发的灵活性。

  • 使用预加载的方式加载已有的规则,并将加载后的对象缓存起来,每次规则变动时从新load整条规则,极大的提高了引擎的执行效率

  • 计数器引入AtomicLongFieldUpdater工具类,来减小计数器的内存消耗

  • 灵活的上下文使用方式,方便定制规则执行的流程(规则执行顺序、同步异步执行、跳过某些规则、规则集短路等),灵活定义返回结果(能够返回整个上下文,能够返回每条规则的结果,也能够返回最后一条规则的结果),这些均可以经过设置上下文来实现。

  • groovy的方法查找策略,默认是从metaClass里面查找,再从上下文里找,为了提高性能,咱们重写了metaClass,修改了这个查询逻辑:先从上下文里找,再从metaClass里面找。


规则配置以下图所示:


Alt pic


将来规划

后面规则引擎平台主要会围绕下面几点来作:


  1. 指标存储计划从redis切换到hbase。目前的指标计算方式会致使缓存key的暴涨,获取一个指标值可能须要N个key来作累加,而换成hbase以后,一个指标就只须要一条记录来维护,使用hbase的列族来实现滑动窗口的计算。

  2. 规则的灰度上线。当一条新规则建立后,若是不进行灰度的测试,直接上线是可能会带来灾难的。后面再规则上线流程中新增灰度上线环节,整个引擎会根据配置的灰度比例,复制必定的流量到灰度规则中,并对灰度规则的效果进行展现,达到预期效果并稳定后才能审批上线。

  3. 事件接入的自动化。dubbo这块能够采用泛化调用,http接口须要统一调用标准,消息须要统一格式。有了统一的标准就能够实现事件自动接入而不须要修改代码上线,这样也能够保证整个引擎的稳定性。

  4. 模型生命周期管理。目前模型这块都是经过在猛犸平台上提交jar包的方式,离线跑一个model出来,没有一个统一的平台去管控整个模型的生命周期。如今杭研已经有相似的平台了,后续须要考虑如何介入。

  5. 数据展现优化。如今整个平台的数字化作的比较弱,无法造成数据驱动业务。而风控的运营每每是须要大量的数据去驱动规则的优化的,好比规则阈值的调试、规则命中率、风险大盘等都须要大量数据的支撑。


网易云免费体验馆,0成本体验20+款云产品!

更多网易技术、产品、运营经验分享请点击


相关文章:
【推荐】 【网易严选】iOS持续集成打包(Jenkins+fastlane+nginx)
【推荐】 基于云原生的秒杀系统设计思路
【推荐】 一行代码搞定Dubbo接口调用

相关文章
相关标签/搜索