背景
用户来闲鱼,主要是为了获得自己关心的内容。随着闲鱼的体量越来越大,内容也变得越来越丰富。闲鱼基于用户画像,可以将用户关心的内容推送给用户。具体在哪些场景下才需要触发推送?我们定义了很多触发规则,包括停留时长、点击路径等。
起初我们把触发规则的逻辑放在服务端(Blink)运行。但实践下来发现Blink存在诸多限制:
服务端要对客户端埋点进行数据清洗,考虑到闲鱼的DAU已经突破2000w,这个量是非常庞大的,非常消耗服务端资源;
Blink的策略是实时执行的,同样因为资源问题,现在只能同时上线十几个策略。
如何解决这些问题呢,我们开始考虑能否将Blink的策略跑在客户端!
CEP模型
Blink,作为是Flink的一个分支,最初是阿里巴巴内部创建的,针对Flink进行了改进,所以我们这里还是围绕Flink讨论。CEP(Complex Event Process)是Flink中的一个子库,用来快速检测无尽数据流中的复杂模式。
Flink CEP
Flink的CEP的核心是NFA(Non-determined Finite Automaton),全称叫不确定的有限状态机。提到NFA,就不得不提Jagrati Agrawal等撰写的关于NFA模型的论文《Efficient Pattern Matching over Event Streams》,本篇论文中描述了NFA的匹配原理。
上面这张图,就是一个不确定的有限状态机,它由状态(State)还有之间的连线(StateTransition)组成的。
状态(State):状态是根据flink脚本里面的代码来决定的,最终会有一个
$end$
的Final状态转换(StateTransition):State的转换条件,包括
take/proceed/ignore
不同的条件,代表的含义不同:
take
: 满足条件,获取当前元素,进入下一状态proceed
:不论是否满足条件,不获取当前元素,直接进入下状态(如optional)并进行判断是否满足条件。ignore
:不满足条件,忽略,进入下一状态。
我们只要在端上实现这样一个状态机,就可以实现一个CEP引擎。
Python CEP
对于客户端来说,首先要解决的问题是如何构建一个CEP环境。经过调研,可以复用集团的端智能容器(Walle),作为Python容器可以执行cep的策略。
在构建NFA之前,首先要解决的一个问题是数据来源,手淘信息流团队有一套完整的解决方案BehaviX/BehaviR,可以对UT埋点进行结构化,能很好的结合Walle容器来触发策略。有了事件来源,还需要解决的是Python脚本如何执行。Walle平台可以将多个Python脚本打包下载并执行,因此,我们可以将CEP封装成一个Python的库,然后跟策略脚本一起下发。
最终的整体架构设计如下图所示:
本文重点介绍下如何用Python来实现一个CEP的编译器,这个编译器主要用来将CEP的描述语言转换成为NFA。
编译器原理
在Flink中,java侧会有一套完善的API来编写一个策略脚本,《efficient Pattern Matching over Event Streams》论文中还定义了一套完备的DSL描述语言,也是会转化成java文件去调用这些API去完成匹配。那么接下来会重点讨论,flink是如何将上述API转化成NFA去匹配,以及Python CEP如何实现上述一套完整API接口。
Pattern
在Flink里面,是通过 Pattern
来构建这个NFA,首先用它描述这个不确定性状态机。首先是构建一个 Pattern
的一个链表,得到这个链表之后,会将每个Pattern映射成为 State
的图,点与点之间会通过 StateTransition
来连接。以下面的Pyt