闲鱼如何打造高效CEP系统及DSL编程语言

avatar
@阿里巴巴集团
原文链接: mp.weixin.qq.com

背景

复杂事件处理(Complex Event Processing,以下简称CEP)是一种分析事件流的技术,事件可以是事物有意义的状态变化也可以是事物之间的动作。主要用于分析事件之间的关联关系,且这种关联关系在时间上是多种多样的。

在闲鱼内部,需要处理复杂事件的场景逐渐增多。比如:1. 安全治理领域:用户5分钟内发送同一图片或文字消息给100个不同的人,禁言该用户。 卖家发布商品,买家5秒内拍下商品。 1小时内,同一买家超过5次。处罚该买家。2. 营销领域: 12小时内,买家查看商品详情,卖家降价商品。给买家发送降价push。这些需求常常多变的,而且需要快速上线验证,如何提升开发效率同时满足事件计算的及时性、可靠性、稳定性成为摆在闲鱼面前的一大问题。

CEP系统的构建思路

CEP技术是一项历史比较久的技术,早年就被应用于很多计算机领域-比如应用于无线射频识别(Radio Frequency Identification,RFID)领域的的事件监控预警。对闲鱼来应用场景来说,总结核心诉求如下:

1. 需要满足常见的计算需求:需要考虑事件序列、窗口聚合、事件过滤、模式匹配计算场景。

2. 开发效率:需求常常是多变的,要求可以低成本开发快速上线。

3. 性能:闲鱼存在海量的用户行为数据以及各种预定义事件,要求千万级/秒的事件处理吞吐量,秒级延迟。

4. 容错性:数据不可丢,计算不可以出错,出现错误后系统可自动恢复。

5. 云端联动:有一些简单计算场景可以直接在端上实现掉,提升响应性能。只有复杂场景才需要在服务端计算实现。

6. 能够解决大部分计算场景就可以了,不需要为了一些非常少见的场景去设计特别复杂的机制。

主流开源CEP技术框架

目前主流的CEP技术花样繁多,各成体系,我们调研了业内主流开源技术方案做了一些比较:

闲鱼CEP系统的思路

调研众多技术方案和参考相关论文后,闲鱼决定采用如下的方式构建CEP系统:

1. 标准化事件的输入与输出,这样可以大幅度降低系统的复杂性。输出事件写入消息服务中供使用方订阅。

2. 为了简化开发工作,同时统一云和端的匹配规则表达,参考业内论文,我们决定自定义一种简单的DSL语言来描述规则。该语言应该类似SQL,由有限的关键字组成,让普通开发者一看就懂,同时有足够的表达能力覆盖大部分闲鱼CEP计算场景。

3. 关于计算引擎的选择:考虑到系统需要处理海量的用户行为数据,对性能、实时性、容错性的严苛要求以及人员技术熟悉情况。选择阿里内部较为成熟Blink(开源版本叫Flink)实时计算框架作为底层计算引擎。

设计实现

输入输出

整个系统本质上是一个数据处理系统,自然首先需要定义的事情就是:输入与输出。

我们抽象了闲鱼的常见的用户行为和动作,作为基本事件,并把这些基本事件标准化。这些基本事件可以看作是我们CEP计算的输入,由基本事件匹配生成的结果可以看作是输出事件。1. 事件标准化输入:

    输入样例:

    event_code: S_SEND_MSG

    event_time: 2019-09-24 10:15:23.474

    extra_info: {"to_user":"ccc","ARG3":"0",

    "SDKTYPE":"mini","UTPVID_T":"156*****104","ARG1":"xchat",

    "chat_session_type":"1","APPKEY":"***","EVENTID":"***",

    "PAGE":"UT","_priority":"4","chat_session_id":"*****",

    "content_type":"1",

    "text":"******"}

    user_id: abc

2. 事件的标准化输出:默认输出到消息服务中。消息体是由输出字段组成的json kv结构

                                

    输入样例:

    { "user_id":"abc" ,"event_time": "2019-09-24 10:15:23"}

运行架构

我们定义了一种简单的DSL语言来表达事件处理规则,有了DSL,用户可以像使用普通的数据库一样编写DSL,然后提交给系统自动运行该DSL语言,运行该DSL的系统持续的监听输入数据做实时匹配,并将结果作为输出事件输出。Interactive Service: 系统对外提交的交互服务。Strean Source:标准化的事件来源,使用阿里云sls存储。EPL Parser: 负责DSL的语法扫描与代码翻译,将用户编写的DSL解析生成Blink CEP Pattern代码或者Blink SQL。Job Manager:主要负责将生成的EPL Parser生成的代码或SQL部署到Blink的任务中,包括生成任务与执行计划、提交任务、停止任务、启动任务等等。 Sink输入:事件命中计算规则后生成json格式的输出事件,存储到消息服务MetaQ中。

闲鱼DSL语言

我们设计DSL语言的第一个原则就是尽量和SQL语法类似,第二原则是要足够的简单明了。结合闲鱼的使用场景,我们定义的主要语言要素如下:

                                    

    --语言定义

    RULENAME <规则名称> --定义规则名称

    <变量名:事件类型> --定义事件变量

    EVENT <事件模式序列> --定义事件匹配模式

    [WHERE] <条件表达式> --定义事件过滤条件

    [REPEAT] <频次表达式> [SAME] <指定REPEAT重复计数字段> [HAVING] <条件表达式> --定义频次表达

    [WITHIN] <时间窗口> --定义匹配事件窗口

    RETUEN <输出字段> --定义输出事件的字段

    支持场常见内置函数:SUM、COUNT、MAX、MIN、DISTINCT

整个语言的定义比较简洁,熟悉SQL的同学非常容易理解其所要表达的含义。其特点如下:

1. 简化了复杂事件处理的表达。

2. 标准化的事件输入与输出。

3. 语法与关键词含义类SQL。

4. 统一表达方便云端共用。

5. 只表达事件的处理、触发计算,包含基本的过滤、聚合、模式匹配。

6. 支持常见SQL聚合函数。

7. 非通用编程语言,不覆盖所有CEP场景。

编程示例:

    --用户,在10分钟内,发送了相同条消息,给了100不同的用户。

    RULENAME: "示例"

    e1: S_SEND_MSG

    EVENT e1

    REPEAT 100+ SAME e1.user_id HAVING DISTINCT(e1.extra_info[received_user_id]) > 100

    WITHIN 10 MINUTE

    RETURN userId=e1.user_id

DSL语言实现简介

该DSL语言在端上的实现还在开发中,本文只介绍其在云端的实现方式。由于我们选择的计算引擎是Blink,自然该语言需要能够运行在Blink平台上。Blink底层已经支持CEP计算,其JAVA API说明可参考: https://ci.apache.org/projects/flink/flink-docs-release-1.9/dev/libs/cep.html。在Blink内部是通过NFA(Non-determined Finite Automaton 不确定有限状态机)来实现CEP技术的,其主要理论依据参考的是论文 Efficient Pattern Matching over Event Streams中介绍的模型,有兴趣的同学可以参考。

我们知道标准的SQL是可以直接运行在Blink上面的,Blink没有自己构建一个SQL优化解析器,而是很聪明的选择了Apache Calcite来实现SQL校验、SQL解析、抽象语法树的构建以及SQL优化(底层语法分析实际是通过Javacc实现)。Calcite是一个非常流行的开源SQL实现框架,在很多开源项目中都有应用。 这样Calcite在Blink SQL架构中处于核心地位。闲鱼的DSL相当于扩展了Blink的SQL,通过自定义解析器做DSL的解析、校验同时生成抽象语法树,然后根据语法树生成Blink CEP代码。特别地对于一种简单场景: 只有对一种类型的事件做过滤、聚合计算的情况;我们将其直接翻译成Blink SQL,因为Blink SQL底层是Stream API比Blink CEP基于NFA的状态机性能要好。整体架构如下图所示:

实现流程:

1. 利用calcite定制解析器解析DSL语言,生成类SQL语法树。

2. 语法正确性校验。

3. 通过语法正确性校验生成抽象语法树。

4. 判断如果只有一种类型的事件,通过代码替换模板直接翻译成Blink SQL 转7,否则转5。

5. 翻译成CEP Pattern API代码。

6. 添加标准的输入、输出Stream,构建完整的运行拓扑图。

7. 设定任务运行参数。

8. 通过Blink API提交Blink运行。

应用效果

目前该系统已经上线第一个版本,承接了闲鱼的安全策略检查、实时触达用户以及玩法场景下的规则实时匹配。生成的匹配结果通过写入MetaQ供使用方消费。

效率提升:初步验证实现同样的规则匹配功能,同编写JAVA代码相比使用改DSL语言可以大幅提升开发效率,从接受需求到编写DSL上线验证一般30分钟左右即可完成。 性能:DSL生成的计算任务处理10w左右QPS数据,消耗3个cu,平均延迟1s。 高可靠性:依赖于Blink的高可靠特性,任务的运行自然拥有快速的错误恢复机制以及数据乱序处理能力。 实际运行效果如下图所示:闲鱼的CEP计算还在不断完善中,同时我们在和Blink团队合作共建该DSL语言,成为Blink应用生态的一部分。 计划成熟后将逐步应用于阿里内部其他BU。

闲鱼团队是Flutter+Dart FaaS前后端一体化新技术的行业领军者,就是现在!客户端/服务端java/架构/前端/质量工程师 面向社会招聘,base杭州阿里巴巴西溪园区,一起做有创想空间的社区产品、做深度顶级的开源项目,一起拓展技术边界成就极致!

*投喂简历给小闲鱼→guicai.gxy@alibaba-inc.com

更多系列文章、开源项目、关键洞察、深度解读

请认准 闲鱼技术