Flink之CEP-API分析

Posted Flink

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flink之CEP-API分析相关的知识,希望对你有一定的参考价值。

CEP API的核心是Pattern API,它允许你快速定义复杂的事件模式。每个模式包含多个阶段(stage)或者我们也可称为状态(state)。为了从一个状态切换到另一个状态,用户可以指定条件,这些条件可以作用在邻近的事件或独立事件上。

Pattern在外部无法通过构造器进行实例化,构造器的访问限定符是protected的,因此Pattern对象只能通过begin和next以及followedBy(用于创建其派生类FollowedByPattern)来创建,在创建时需要指定其名称。

每个模式必须以一个初始状态开始且必须指定唯一的名称来标识被匹配的事件:

Pattern<Event, ?> start = Pattern.<Event>begin("start");

通过where方法可以为起始状态指定一个过滤条件:

start.where(new FilterFunction<Event>() {
    @Override
    public boolean filter(Event value) {
        return ... // some condition
    }
});

当然,也可以严格地限制接收事件为初始化的事件类型(Event)的子类型(SubEvent),通过subtype方法:

start.subtype(SubEvent.class).where(new FilterFunction<SubEvent>() {
    @Override
    public boolean filter(SubEvent value) {
        return ... // some condition
    }
});

正如你所看到的,子类型条件也可以与在子类型上的一个额外的过滤条件合并。事实上,你可以通过多次调用where以及subtype方法并指定多个条件,这些条件将会通过逻辑“与”运算符合并。在构建匹配条件时,CEP提供了两个函数类:AndFilterFunction以及SubtypeFilterFunction。其中SubtypeFilterFunction专用于subtype API来判定事件的类型是否符合要求。AndFilterFunction则是通用的逻辑“与”运算符来连接左右表达式。其类图如下:

图中展示了它的构造器,它可以注入两个FilterFunction函数的实例,分别作为逻辑与的左表达式和右表达式。由于AndFilterFunction扩展了接口FilterFunction,而当调用where或subtype API时其实都是在不断扩充左表达式:

Flink之CEP-API分析

接下来,我们可以追加更多的状态来构建更复杂的模式,多个状态的转换涉及到模式对事件的选择策略:

Pattern当前支持严格邻近和非严格邻近这两种事件选择策略。事件选择策略在Pattern的API上通过如下两个方法来指定:

  • next:会追加一个新的Pattern对象到既有的Pattern之后,它表示当前模式运算符所匹配的事件必须是严格紧邻的,这意味着两个被匹配的事件必须是前后紧邻,中间没有其他元素;
  • followedBy:会追加一个新的Pattern到既有的Pattern之后(其实返回的是一个FollowedByPattern对象,它是Pattern的派生类),它表示当前运算符所匹配的事件不必严格紧邻,这意味着其他事件被允许穿插在匹配的两个事件之间;

事实上Flink的CEP实现简化了论文里提及的四种事件选择策略。

创建一个新的严格邻近的Pattern:

Pattern<Event, ?> strictNext = start.next("middle");

创建一个非严格邻近的Pattern:

Pattern<Event, ?> nonStrictNext = start.followedBy("middle");

一个复杂的CEP程序可能其模式也较为复杂,而多个Pattern之间通过前向指针建立连接关系从而形成“模式链”,形如下图:

Flink之CEP-API分析

也可以通过within API为模式定义一个时间约束(也即时间窗口),它表示第一个元素和最后一个元素之间的时间间隔不能超过窗口时间。例如,通过within定义一个模式匹配必须发生在10秒之内。

next.within(Time.seconds(10));

为了在你的事件流上运行模式检测,你得创建一个PatternStream。给定一个输入流input以及一个模式pattern(很有可能是一个模式链的头),你可以通过如下的示例代码创建一个PatternStream:

DataStream<Event> input = ...
Pattern<Event, ?> pattern = ...

PatternStream<Event> patternStream = CEP.pattern(input, pattern);

一旦你获得PatternStream你可以从匹配的事件序列中通过select API或者flatSelect API选择匹配的事件。select API要求一个PatternSelectFunction函数的实现。PatternSelectFunction有一个select方法,会被每个匹配的事件序列调用。它接收一个匹配事件的”状态名/事件”对映射,并恰好返回一个结果:

class MyPatternSelectFunction<IN, OUT> implements PatternSelectFunction<IN, OUT> {
    @Override
    public OUT select(Map<String, IN> pattern) {
        IN startEvent = pattern.get("start");
        IN endEvent = pattern.get("end");

        return new OUT(startEvent, endEvent);
    }
}

PatternFlatSelectFunction跟PatternSelectFunction类似,唯一的区别就是它可以返回任意数量的结果。为了做到这一点,select方法带了一个额外的Collector参数用来输出元素:

class MyPatternFlatSelectFunction<IN, OUT> implements PatternFlatSelectFunction<IN, OUT> {
    @Override
    public void select(Map<String, IN> pattern, Collector<OUT> collector) {
        IN startEvent = pattern.get("start");
        IN endEvent = pattern.get("end");

        for (int i = 0; i < startEvent.getValue(); i++ ) {
            collector.collect(new OUT(startEvent, endEvent));
        }
    }
}

下一小篇我们将以一个示例来展示这些API的应用。