Apache Flink CEP 实战

下面以一个打车的例子来展示状态是如何流转的,规则见下图所示。

以乘客制定行程作为开始,匹配乘客的下单事件,如果这个订单超时还没有被司机接单的话,就把行程事件和下单事件作为结果集往下游输出。

假如消息到来顺序为:行程–>其他–>下单–>其他。

状态流转如下:

  1. 开始时状态处于行程状态,即等待用户制定行程。

  1. 当收到行程事件时,匹配行程状态的条件,把行程事件放到结果集中,通过 take 边将状态往下转移到下单状态。 

  2. 由于下单状态上有一条 ignore 边,所以可以忽略收到的其他事件,直到收到下单事件时将其匹配,放入结果集中,并且将当前状态往下转移到超时未接单状态。这时候结果集当中有两个事件:制定行程事件和下单事件。

  1. 超时未接单状态时,如果来了一些其他事件,同样可以被 ignore 边忽略,直到超时事件的触发,将状态往下转移到最终状态,这时候整个模式匹配成功,最终将结果集中的制定行程事件和下单事件输出到下游。

上面是一个匹配成功的例子,如果是不成功的例子会怎么样?

假如当状态处于超时未接单状态时,收到了一个接单事件,那么就不符合超时未被接单的触发条件,此时整个模式匹配失败,之前放入结果集中的行程事件和下单事件会被清理。

Flink CEP 程序开发

本节将详细介绍 Flink CEP 的程序结构以及 API。

1.Flink CEP 程序结构

主要分为两部分:定义事件模式和匹配结果处理。

官方示例如下:

DataStream input = …Pattern<Event, ?> pattern = Pattern.begin(“start”).where( new SimpleCondition() { @Override public boolean filter(Event event) { return event.getId() == 42; } } ).next(“middle”).subtype(SubEvent.class).where( new SimpleCondition() { @Override public boolean filter(SubEvent subEvent) { return subEvent.getVolume() >= 10.0; } } ).followedBy(“end”).where( new SimpleCondition() { @Override public boolean filter(Event event) { return event.getName().equals(“end”); } } );

PatternStream patternStream = CEP.pattern(input, pattern);

DataStream result = patternStream.select( new PatternProcessFunction<Event, Alert>() { @Override public void select( Map<String, List> pattern, Context ctx, Collector out) throws Exception { out.collect(createAlertFrom(pattern)); } });

**程序结构分为三部分:**首先需要定义一个模式(Pattern),即第 2 行代码所示,接着把定义好的模式绑定在 DataStream 上(第 25 行),最后就可以在具有 CEP 功能的 DataStream 上将匹配的结果进行处理(第 27 行)。下面对关键部分做详细讲解:

  • **定义模式:**上面示例中,分为了三步,首先匹配一个 ID 为 42 的事件,接着匹配一个体积大于等于 10 的事件,最后等待收到一个 name 等于 end 的事件。

  • **匹配结果输出:**此部分,需要重点注意 select 函数(第 30 行,注:本文基于 Flink 1.7 版本)里边的 Map 类型的 pattern 参数,Key 是一个 pattern 的 name,它的取值是模式定义中的 Begin 节点 start,或者是接下来 next 里面的 middle,或者是第三个步骤的 end。后面的 map 中的 value 是每一步发生的匹配事件。因在每一步中是可以使用循环属性的,可以匹配发生多次,所以 map 中的 value 是匹配发生多次的所有事件的一个集合。

2.Flink CEP 构成

上图中,蓝色方框代表的是一个个单独的模式;浅黄色的椭圆代表的是这个模式上可以添加的属性,包括模式可以发生的循环次数,或者这个模式是贪婪的还是可选的;橘色的椭圆代表的是模式间的关系,定义了多个模式之间是怎么样串联起来的。通过定义模式,添加相应的属性,将多个模式串联起来三步,就可以构成了一个完整的 Flink CEP 程序。

2.1 定义模式

下面是示例代码:

pattern.next(“start”).where(

new SimpleCondition() {

@Override

public boolean filter(Event event) {

return event.getId() == 42;

}

}

)

定义模式主要有如下 5 个部分组成:

  • pattern:前一个模式

  • next/followedBy/…:开始一个新的模式

  • start:模式名称

  • where:模式的内容

  • filter:核心处理逻辑

2.2 模式的属性

接下来介绍一下怎样设置模式的属性。模式的属性主要分为循环属性和可选属性。

  • 循环属性可以定义模式匹配发生固定次数(times),匹配发生一次以上(oneOrMore),匹配发生多次以上(timesOrMore)。

  • 可选属性可以设置模式是贪婪的(greedy),即匹配最长的串,或设置为可选的(optional),有则匹配,无则忽略。

2.3 模式的有效期

由于模式的匹配事件存放在状态中进行管理,所以需要设置一个全局的有效期(within)。若不指定有效期,匹配事件会一直保存在状态中不会被清除。至于有效期能开多大,要依据具体使用场景和数据量来衡量,关键要看匹配的事件有多少,随着匹配的事件增多,新到达的消息遍历之前的匹配事件会增加 CPU、内存的消耗,并且随着状态变大,数据倾斜也会越来越严重。

2.4 模式间的联系

主要分为三种:严格连续性(next/notNext),宽松连续性(followedBy/notFollowedBy),和非确定宽松连续性(followedByAny)。

三种模式匹配的差别见下表所示:

| 模式&数据流 | 严格连续性 | 宽松连续性 | 非确定宽松连续性 |

| — | — | — | — |

|

Pattern(A B) Streaming(‘a’,‘c’,‘b1’,‘b2’)

|

不匹配

|

匹配 输出:a,b1

|

匹配 输出:a,b1 a,b2

|

总结如下:

  • 严格连续性:需要消息的顺序到达与模式完全一致。

  • 宽松连续性:允许忽略不匹配的事件。

  • 非确定宽松连性:不仅可以忽略不匹配的事件,也可以忽略已经匹配的事件。

2.5 多模式组合

除了前面提到的模式定义和模式间的联系,还可以把相连的多个模式组合在一起看成一个模式组,类似于视图,可以在这个模式视图上进行相关操作。

上图这个例子里面,首先匹配了一个登录事件,然后接下来匹配浏览,下单,购买这三个事件反复发生三次的用户。

如果没有模式组的话,代码里面浏览,下单,购买要写三次。有了模式组,只需把浏览,下单,购买这三个事件当做一个模式组,把相应的属性加上 times(3) 就可以了。

2.6 处理结果

处理匹配的结果主要有四个接口:PatternFlatSelectFunction,PatternSelectFunction,PatternFlatTimeoutFunction 和 PatternTimeoutFunction。

从名字上可以看出,输出可以分为两类:select 和 flatSelect 指定输出一条还是多条,timeoutFunction 和不带 timeout 的 Function 指定可不可以对超时事件进行旁路输出。

下图是输出的综合示例代码:

2.7 状态存储优化

当一个事件到来时,如果这个事件同时符合多个输出的结果集,那么这个事件是如何保存的?

Flink CEP 通过 Dewey 计数法在多个结果集中共享同一个事件副本,以实现对事件副本进行资源共享。


Flink CEP 的扩展

本章主要介绍一些 Flink CEP 的扩展,讲述如何做到超时机制的精确管理,以及规则的动态加载与更新。

1.超时触发机制扩展

原生 Flink CEP 中超时触发的功能可以通过 within+outputtag 结合来实现,但是在复杂的场景下处理存在问题,如下图所示,在下单事件后还有一个预付款事件,想要得到下单并且预付款后超时未被接单的订单,该如何表示呢?

参照下单后超时未被接单的做法,把下单并且预付款后超时未被接单规则表示为下单**.followedBy(预付款).followedBy(接单).within(time)**,那么这样实现会存在问题吗?

这种做法的计算结果是会存在脏数据的,因为这个规则不仅匹配到了下单并且预付款后超时未被接单的订单(想要的结果),同样还匹配到了只有下单行为后超时未被接单的订单(脏数据,没有预付款)。原因是因为超时 within 是控制在整个规则上,而不是某一个状态节点上,所以不论当前的状态是处在哪个状态节点,超时后都会被旁路输出。

那么就需要考虑能否通过时间来直接对状态转移做到精确的控制,而不是通过规则超时这种曲线救国的方式。于是乎,在通过消息触发状态的转移之外,需要增加通过时间触发状态的转移支持。要实现此功能,需要在原来的状态以及状态转移中,增加时间属性的概念。如下图所示,通过 wait 算子来得到 waiting 状态,然后在 waiting 状态上设置一个十秒的时间属性以定义一个十秒的时间窗口。

wait 算子对应 NFA 中的 ignore 状态,将在没有到达时间窗口结束时间时自旋,在 ComputationState 中记录 wait 的开始时间,在 NFA 的 doProcess 中,将到来的数据与waiting 状态处理,如果到了 waiting 的结束时间,则进行状态转移。

上图中红色方框中为 waiting 状态设置了两条 ignore 边:

  1. waitingStatus.addIgnore(lastSink,waitingCondition),waitingCondition 中的逻辑是获取当前的时间(支持事件时间),判断有没有超过设置的 waiting 阈值,如果超过就把状态向后转移。

  2. waitingStatus.addIgnore(waitingCondition),waitingCondition 中如果未达到设置的 waiting 阈值,就会自旋在当前的 waiting 状态不变。

2.规则动态注入

线上运行的 CEP 中肯定经常遇到规则变更的情况,如果每次变更时都将任务重启、重新发布是非常不优雅的。尤其在营销或者风控这种对实时性要求比较高的场景,如果规则窗口过长(一两个星期),状态过大,就会导致重启时间延长,期间就会造成一些想要处理的异常行为不能及时发现。

那么要怎么样做到规则的动态更新和加载呢?

总结

我个人认为,如果你想靠着背面试题来获得心仪的offer,用癞蛤蟆想吃天鹅肉形容完全不过分。想必大家能感受到面试越来越难,想找到心仪的工作也是

必看视频!获取2024年最新Java开发全套学习资料 备注Java

越来越难,高薪工作羡慕不来,却又对自己目前的薪资不太满意,工作几年甚至连一个应届生的薪资都比不上,终究是错付了,错付了自己没有去提升技术。

这些面试题分享给大家的目的,其实是希望大家通过大厂面试题分析自己的技术栈,给自己梳理一个更加明确的学习方向,当你准备好去面试大厂,你心里有底,大概知道面试官会问多广,多深,避免面试的时候一问三不知。

大家可以把Java基础,JVM,并发编程,MySQL,Redis,Spring,Spring cloud等等做一个知识总结以及延伸,再去进行操作,不然光记是学不会的,这里我也提供一些脑图分享给大家:

希望你看完这篇文章后,不要犹豫,抓紧学习,复习知识,准备在明年的金三银四拿到心仪的offer,加油,打工人!
不来,却又对自己目前的薪资不太满意,工作几年甚至连一个应届生的薪资都比不上,终究是错付了,错付了自己没有去提升技术。

这些面试题分享给大家的目的,其实是希望大家通过大厂面试题分析自己的技术栈,给自己梳理一个更加明确的学习方向,当你准备好去面试大厂,你心里有底,大概知道面试官会问多广,多深,避免面试的时候一问三不知。

大家可以把Java基础,JVM,并发编程,MySQL,Redis,Spring,Spring cloud等等做一个知识总结以及延伸,再去进行操作,不然光记是学不会的,这里我也提供一些脑图分享给大家:

[外链图片转存中…(img-lSkphIkJ-1716435939364)]

[外链图片转存中…(img-Wo1GiluS-1716435939365)]

[外链图片转存中…(img-FAJQsGSF-1716435939365)]

希望你看完这篇文章后,不要犹豫,抓紧学习,复习知识,准备在明年的金三银四拿到心仪的offer,加油,打工人!

  • 24
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值