文章目录

  • Flink 系列文章
  • 二、模式API
  • 1、单个模式
  • 2)、条件
  • 1、迭代条件
  • 2、简单条件
  • 3、组合条件
  • 4、停止条件
  • 3)、where(condition)
  • 4)、or(condition)
  • 5)、until(condition)
  • 6)、subtype(subClass)
  • 7)、oneOrMore()
  • 8)、timesOrMore(#times)
  • 9)、times(#ofTimes)
  • 10)、times(#fromTimes, #toTimes)
  • 11)、optional()
  • 12)、greedy()
  • 2、组合模式
  • 1)、组合模式介绍

  • 2)、循环模式中的连续性
  • 3、模式组
  • 4、匹配后跳过策略



本文介绍了Flink 的类库CEP的单个模式中条件部分、组合模式、模式组和匹配后的跳过策略。

二、模式API

1、单个模式

2)、条件

对每个模式你可以指定一个条件来决定一个进来的事件是否被接受进入这个模式,例如,它的value字段应该大于5,或者大于前面接受的事件的平均值。 指定判断事件属性的条件可以通过pattern.where()、pattern.or()或者pattern.until()方法。 这些可以是IterativeCondition或者SimpleCondition。

1、迭代条件

这是最普遍的条件类型。使用它可以指定一个基于前面已经被接受的事件的属性或者它们的一个子集的统计数据来决定是否接受时间序列的条件。

在 Flink CEP 中,提供了 IterativeCondition 抽象类。这其实是更加通用的条件表达,查看源码可以发现, .where()方法本身要求的参数类型就是 IterativeCondition;而之前 的SimpleCondition 是它的一个子类。

在 IterativeCondition 中同样需要实现一个 filter()方法,不过与 SimpleCondition 中不同的是,这个方法有两个参数:除了当前事件之外,还有一个上下文 Context。调用这个上下文的.getEventsForPattern()方法,传入一个模式名称,就可以拿到这个模式中已匹配到的所有数据了。

下面是一个迭代条件的代码,它接受"first"模式下一个事件的userid开头是"1001", 并且前面已经匹配到的事件登录状态改为S。 迭代条件非常强大,尤其是跟循环模式结合使用时。

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.cep.CEP;
import org.apache.flink.cep.PatternSelectFunction;
import org.apache.flink.cep.PatternStream;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.conditions.IterativeCondition;
import org.apache.flink.cep.pattern.conditions.SimpleCondition;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/*
 * @Author: alanchan
 * @LastEditors: alanchan
 * @Description: 
 */
public class TestCEPDemo {
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class LoginEvent {
        private Integer userId;
        private String ip;
        private String status;
        private Long timestamp;

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof LoginEvent) {
                LoginEvent loginEvent = (LoginEvent) obj;
                return this.userId == loginEvent.getUserId() && this.ip.equals(loginEvent.ip)
                        && this.status.equals(loginEvent.getStatus()) && this.timestamp == loginEvent.getTimestamp();
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return super.hashCode() + Long.hashCode(timestamp);
        }
    }

    final static List<LoginEvent> loginEventList = Arrays.asList(
            new LoginEvent(1001, "192.168.10.1", "F", 2L),
            new LoginEvent(1001, "192.168.10.2", "F", 3L),
            new LoginEvent(1002, "192.168.10.8", "F", 4L),
            new LoginEvent(1001, "192.168.10.6", "F", 5L),
            new LoginEvent(1002, "192.168.10.8", "F", 7L),
            new LoginEvent(1002, "192.168.10.8", "F", 8L),
            new LoginEvent(1002, "192.168.10.8", "S", 6L),
            new LoginEvent(1003, "192.168.10.8", "F", 6L),
            new LoginEvent(1004, "192.168.10.3", "S", 4L));

    static void test2() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 按用户id分组
        DataStream<LoginEvent> loginEventDS = env.fromCollection(loginEventList).assignTimestampsAndWatermarks(
                WatermarkStrategy.<LoginEvent>forBoundedOutOfOrderness(Duration.ofSeconds(10))
                        .withTimestampAssigner((loginEvent, rs) -> loginEvent.getTimestamp()))
                .keyBy(loginEvent -> loginEvent.getUserId());

        // 定义模式
        Pattern<LoginEvent, ?> loginEventPattern = Pattern.<LoginEvent>begin("first")
                .where(new SimpleCondition<LoginEvent>() {

                    @Override
                    public boolean filter(LoginEvent value) throws Exception {
                        return value.getStatus().equals("F");
                    }

                })
                .next("second")
                .where(new IterativeCondition<TestCEPDemo.LoginEvent>() {

                    @Override
                    public boolean filter(LoginEvent value, Context<LoginEvent> ctx) throws Exception {
                        Iterable<LoginEvent> loginEventIterable = ctx.getEventsForPattern("first");
                        for (LoginEvent loginEvent : loginEventIterable) {
                            if (loginEvent.getUserId() == 1001) {
                                loginEvent.setStatus("S");
                            }
                        }
                        return value.getStatus().equals("F");
                    }

                });

        // 将Pattern应用到流上,检测匹配的复杂事件,得到一个PatternStream
        PatternStream<LoginEvent> patternStream = CEP.pattern(loginEventDS, loginEventPattern);

        // 将匹配到的流选择出来输出
        patternStream
                .select(new PatternSelectFunction<LoginEvent, String>() {
                    @Override
                    public String select(Map<String, List<LoginEvent>> map) throws Exception {
                        return map.get("first").toString()+" \n"+map.get("second").toString();
                    }
                })
                .print("输出信息:\n");

        // 控制台输出:

        env.execute();
    }

    public static void main(String[] args) throws Exception {
        test2();
    }
}

调用ctx.getEventsForPattern(…)可以获得所有前面已经接受作为可能匹配的事件。调用这个操作的代价可能很小也可能很大,所以在实现你的条件时,尽量少使用它。

2、简单条件

这种类型的条件扩展了前面提到的IterativeCondition类,它决定是否接受一个事件只取决于事件自身的属性。

start.where(SimpleCondition.of(value -> value.getName().startsWith("foo")));

你可以通过pattern.subtype(subClass)方法限制接受的事件类型是初始事件的子类型。

start.subtype(SubEvent.class)
    .where(SimpleCondition.of(value -> ... /*一些判断条件*/));
3、组合条件

你可以把subtype条件和其他的条件结合起来使用。这适用于任何条件,你可以通过依次调用where()来组合条件。 最终的结果是每个单一条件的结果的逻辑AND。

如果想使用OR来组合条件,你可以像下面这样使用or()方法。

pattern
    .where(SimpleCondition.of(value -> ... /*一些判断条件*/))
    .or(SimpleCondition.of(value -> ... /*一些判断条件*/));
    

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.cep.CEP;
import org.apache.flink.cep.PatternSelectFunction;
import org.apache.flink.cep.PatternStream;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.conditions.IterativeCondition;
import org.apache.flink.cep.pattern.conditions.SimpleCondition;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/*
 * @Author: alanchan
 * @LastEditors: alanchan
 * @Description: 
 */
public class TestCEPDemo {
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class LoginEvent {
        private Integer userId;
        private String ip;
        private String status;
        private Long timestamp;

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof LoginEvent) {
                LoginEvent loginEvent = (LoginEvent) obj;
                return this.userId == loginEvent.getUserId() && this.ip.equals(loginEvent.ip)
                        && this.status.equals(loginEvent.getStatus()) && this.timestamp == loginEvent.getTimestamp();
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return super.hashCode() + Long.hashCode(timestamp);
        }
    }

    final static List<LoginEvent> loginEventList = Arrays.asList(
            new LoginEvent(1001, "192.168.10.1", "F", 2L),
            new LoginEvent(1001, "192.168.10.2", "F", 3L),
            new LoginEvent(1002, "192.168.10.8", "F", 4L),
            new LoginEvent(1001, "192.168.10.6", "F", 5L),
            new LoginEvent(1002, "192.168.10.8", "F", 7L),
            new LoginEvent(1002, "192.168.10.8", "F", 8L),
            new LoginEvent(1002, "192.168.10.8", "S", 6L),
            new LoginEvent(1003, "192.168.10.8", "F", 6L),
            new LoginEvent(1004, "192.168.10.3", "S", 4L));

    static void test3() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 按用户id分组
        DataStream<LoginEvent> loginEventDS = env.fromCollection(loginEventList).assignTimestampsAndWatermarks(
                WatermarkStrategy.<LoginEvent>forBoundedOutOfOrderness(Duration.ofSeconds(10))
                        .withTimestampAssigner((loginEvent, rs) -> loginEvent.getTimestamp()))
                .keyBy(loginEvent -> loginEvent.getUserId());

        // 定义模式
        Pattern<LoginEvent, ?> loginEventPattern = Pattern.<LoginEvent>begin("first")
                .where(new SimpleCondition<LoginEvent>() {

                    @Override
                    public boolean filter(LoginEvent value) throws Exception {
                        return value.getStatus().equals("F");
                    }

                })
                .or(new IterativeCondition<TestCEPDemo.LoginEvent>() {

                    @Override
                    public boolean filter(LoginEvent value, Context<LoginEvent> ctx) throws Exception {
                        return value.getIp().equals("192.168.10.3");
                    }

                });

        // 将Pattern应用到流上,检测匹配的复杂事件,得到一个PatternStream
        PatternStream<LoginEvent> patternStream = CEP.pattern(loginEventDS, loginEventPattern);

        // 将匹配到的流选择出来输出
        patternStream
                .select(new PatternSelectFunction<LoginEvent, String>() {
                    @Override
                    public String select(Map<String, List<LoginEvent>> map) throws Exception {
                        return map.get("first").toString();
                    }
                })
                .print("输出信息:\n");

        // 控制台输出:

        env.execute();
    }

    public static void main(String[] args) throws Exception {
        test3();
    }
}
4、停止条件

如果使用循环模式(oneOrMore()和oneOrMore().optional()),你可以指定一个停止条件,例如,接受事件的值大于5直到值的和小于50。

为了更好的理解它,看下面的例子。给定

  • 模式:“(a+ until b)” (一个或者更多的"a"直到"b")
  • 到来的事件序列:“a1” “c” “a2” “b” “a3”
  • 输出结果会是: {a1 a2} {a1} {a2} {a3}.

你可以看到{a1 a2 a3}和{a2 a3}由于停止条件没有被输出。

3)、where(condition)

为当前模式定义一个条件。为了匹配这个模式,一个事件必须满足某些条件。 多个连续的 where() 语句取与组成判断条件。

pattern.where(new IterativeCondition<Event>() {
    @Override
    public boolean filter(Event value, Context ctx) throws Exception {
        return ... // 一些判断条件
    }
});

4)、or(condition)

增加一个新的判断,和当前的判断取或。一个事件只要满足至少一个判断条件就匹配到模式。

pattern.where(new IterativeCondition<Event>() {
	    @Override
	    public boolean filter(Event value, Context ctx) throws Exception {
	        return ...; //  一些判断条件
	    }
	}).or(new IterativeCondition<Event>() {
	    @Override
	    public boolean filter(Event value, Context ctx) throws Exception {
	        return ...; // 替代条件 
	    }
});

5)、until(condition)

为循环模式指定一个停止条件。意思是满足了给定的条件的事件出现后,就不会再有事件被接受进入模式了。 只适用于和oneOrMore()同时使用。

在基于事件的条件中,它可用于清理对应模式的状态。

pattern.oneOrMore().until(new IterativeCondition<Event>() {
    @Override
    public boolean filter(Event value, Context ctx) throws Exception {
        return ...; // 替代条件 
    }
});

static void test4() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 按用户id分组
        DataStream<LoginEvent> loginEventDS = env.fromCollection(loginEventList).assignTimestampsAndWatermarks(
                WatermarkStrategy.<LoginEvent>forBoundedOutOfOrderness(Duration.ofSeconds(10))
                        .withTimestampAssigner((loginEvent, rs) -> loginEvent.getTimestamp()))
                .keyBy(loginEvent -> loginEvent.getUserId());

        // 定义模式
        Pattern<LoginEvent, ?> loginEventPattern = Pattern.<LoginEvent>begin("first")
                // .where(new SimpleCondition<LoginEvent>() {

                //     @Override
                //     public boolean filter(LoginEvent value) throws Exception {
                //         return value.getStatus().equals("F");
                //     }

                // })
                .oneOrMore()
                .until(new IterativeCondition<TestCEPDemo.LoginEvent>() {

                    @Override
                    public boolean filter(LoginEvent value, Context<LoginEvent> ctx) throws Exception {
                        return value.getIp().equals("192.168.10.3");
                    }

                });

        // 将Pattern应用到流上,检测匹配的复杂事件,得到一个PatternStream
        PatternStream<LoginEvent> patternStream = CEP.pattern(loginEventDS, loginEventPattern);

        // 将匹配到的流选择出来输出
        patternStream
                .select(new PatternSelectFunction<LoginEvent, String>() {
                    @Override
                    public String select(Map<String, List<LoginEvent>> map) throws Exception {
                        return map.get("first").toString();
                    }
                })
                .print("输出信息:\n");

        // 控制台输出:

        env.execute();
}

6)、subtype(subClass)

为当前模式定义一个子类型条件。一个事件只有是这个子类型的时候才能匹配到模式。

pattern.subtype(SubEvent.class);

7)、oneOrMore()

指定模式期望匹配到的事件至少出现一次。 默认(在子事件间)使用松散的内部连续性。 推荐使用 until()或者 within()来清理状态。

pattern.oneOrMore();

8)、timesOrMore(#times)

指定模式期望匹配到的事件至少出现 #times 次。 默认(在子事件间)使用松散的内部连续性。

pattern.timesOrMore(2);

9)、times(#ofTimes)

指定模式期望匹配到的事件正好出现的次数。 默认(在子事件间)使用松散的内部连续性。

pattern.times(2);

10)、times(#fromTimes, #toTimes)

指定模式期望匹配到的事件出现次数在#fromTimes和#toTimes之间。 默认(在子事件间)使用松散的内部连续性。

pattern.times(2, 4);

11)、optional()

指定这个模式是可选的,也就是说,它可能根本不出现。这对所有之前提到的量词都适用。

pattern.oneOrMore().optional();

12)、greedy()

指定这个模式是贪心的,也就是说,它会重复尽可能多的次数。这只对量词适用,现在还不支持模式组。

pattern.oneOrMore().greedy();

2、组合模式

1)、组合模式介绍

模式序列由一个初始模式作为开头,如下所示:

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

接下来,你可以增加更多的模式到模式序列中并指定它们之间所需的连续条件。

FlinkCEP支持事件之间如下形式的连续策略:

  • 严格连续: 期望所有匹配的事件严格的一个接一个出现,中间没有任何不匹配的事件。
  • 松散连续: 忽略匹配的事件之间的不匹配的事件。
  • 不确定的松散连续: 更进一步的松散连续,允许忽略掉一些匹配事件的附加匹配。

可以使用下面的方法来指定模式之间的连续策略:

  • next(),指定严格连续,
  • followedBy(),指定松散连续,
  • followedByAny(),指定不确定的松散连续。

或者

  • notNext(),如果不想后面直接连着一个特定事件
  • notFollowedBy(),如果不想一个特定事件发生在两个事件之间的任何地方。

如果模式序列没有定义时间约束,则不能以 notFollowedBy() 结尾。

一个 NOT 模式前面不能是可选的模式。

// 严格连续
Pattern<Event, ?> strict = start.next("middle").where(...);

// 松散连续
Pattern<Event, ?> relaxed = start.followedBy("middle").where(...);

// 不确定的松散连续
Pattern<Event, ?> nonDetermin = start.followedByAny("middle").where(...);

// 严格连续的NOT模式
Pattern<Event, ?> strictNot = start.notNext("not").where(...);

// 松散连续的NOT模式
Pattern<Event, ?> relaxedNot = start.notFollowedBy("not").where(...);

松散连续意味着跟着的事件中,只有第一个可匹配的事件会被匹配上,而不确定的松散连接情况下,有着同样起始的多个匹配会被输出。 举例来说,模式"a b",给定事件序列"a",“c”,“b1”,“b2”,会产生如下的结果:

  • "a"和"b"之间严格连续: {} (没有匹配),"a"之后的"c"导致"a"被丢弃。
  • “a"和"b"之间松散连续: {a b1},松散连续会"跳过不匹配的事件直到匹配上的事件”。
  • "a"和"b"之间不确定的松散连续: {a b1}, {a b2},这是最常见的情况。

也可以为模式定义一个有效时间约束。

例如,你可以通过pattern.within()方法指定一个模式应该在10秒内发生。 这种时间模式支持处理时间和事件时间.

一个模式序列只能有一个时间限制。如果限制了多个时间在不同的单个模式上,会使用最小的那个时间限制。

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

注意定义过时间约束的模式允许以 notFollowedBy() 结尾。

例如,可以定义如下的模式:

Pattern.<Event>begin("start")
    .next("middle")
    .where(SimpleCondition.of(value -> value.getName().equals("a")))
    .notFollowedBy("end")
    .where(SimpleCondition.of(value -> value.getName().equals("b")))
    .within(Time.seconds(10));

2)、循环模式中的连续性

连续性会被运用在被接受进入模式的事件之间。 用这个例子来说明上面所说的连续性,

一个模式序列"a b+ c"(“a"后面跟着一个或者多个(不确定连续的)“b”,然后跟着一个"c”)

输入为"a",“b1”,“d1”,“b2”,“d2”,“b3”,“c”,

输出结果如下:

  • 严格连续: {a b1 c}, {a b2 c}, {a b3 c} - 没有相邻的 “b” 。
  • 松散连续: {a b1 c},{a b1 b2 c},{a b1 b2 b3 c},{a b2 c},{a b2 b3 c},{a b3 c} - "d"都被忽略了。
  • 不确定松散连续: {a b1 c},{a b1 b2 c},{a b1 b3 c},{a b1 b2 b3 c},{a b2 c},{a b2 b3 c},{a b3 c} - 注意{a b1 b3 c},这是因为"b"之间是不确定松散连续产生的。

对于循环模式(例如oneOrMore()和times())),默认是松散连续。

如果想使用严格连续,你需要使用consecutive()方法明确指定

如果想使用不确定松散连续,你可以使用allowCombinations()方法。

  • consecutive()
    与oneOrMore()和times()一起使用, 在匹配的事件之间施加严格的连续性, 也就是说,任何不匹配的事件都会终止匹配(和next()一样)。
    如果不使用它,那么就是松散连续(和followedBy()一样)。
Pattern.<Event>begin("start")
    .where(SimpleCondition.of(value -> value.getName().equals("c")))
    .followedBy("middle")
    .where(SimpleCondition.of(value -> value.getName().equals("a")))
    .oneOrMore()
    .consecutive()
    .followedBy("end1")
    .where(SimpleCondition.of(value -> value.getName().equals("b")));

// 输入:C D A1 A2 A3 D A4 B
// 会产生下面的输出:
// 如果施加严格连续性: {C A1 B},{C A1 A2 B},{C A1 A2 A3 B}
// 不施加严格连续性: {C A1 B},{C A1 A2 B},{C A1 A2 A3 B},{C A1 A2 A3 A4 B}
  • allowCombinations()
    与oneOrMore()和times()一起使用,在匹配的事件中间施加不确定松散连续性(和followedByAny()一样)。
    如果不使用,就是松散连续(和followedBy()一样)。
Pattern.<Event>begin("start")
    .where(SimpleCondition.of(value -> value.getName().equals("c")))
    .followedBy("middle")
    .where(SimpleCondition.of(value -> value.getName().equals("a")))
    .oneOrMore()
    .allowCombinations()
    .followedBy("end1")
    .where(SimpleCondition.of(value -> value.getName().equals("b")));

// 输入:C D A1 A2 A3 D A4 B
// 会产生如下的输出:
// 如果使用不确定松散连续: {C A1 B},{C A1 A2 B},{C A1 A3 B},{C A1 A4 B},{C A1 A2 A3 B},{C A1 A2 A4 B},{C A1 A3 A4 B},{C A1 A2 A3 A4 B}
// 如果不使用:{C A1 B},{C A1 A2 B},{C A1 A2 A3 B},{C A1 A2 A3 A4 B}

3、模式组

也可以定义一个模式序列作为begin,followedBy,followedByAny和next的条件。

这个模式序列在逻辑上会被当作匹配的条件, 并且返回一个GroupPattern,可以在GroupPattern上使用oneOrMore(),times(#ofTimes), times(#fromTimes, #toTimes),optional(),consecutive(),allowCombinations()。

此处官方的示例好像是不能编译过的,需要将其“?”变为具体的输出类型方可。

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.cep.CEP;
import org.apache.flink.cep.PatternSelectFunction;
import org.apache.flink.cep.PatternStream;
import org.apache.flink.cep.pattern.GroupPattern;
import org.apache.flink.cep.pattern.Pattern;
import org.apache.flink.cep.pattern.conditions.IterativeCondition;
import org.apache.flink.cep.pattern.conditions.SimpleCondition;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/*
 * @Author: alanchan
 * @LastEditors: alanchan
 * @Description: 
 */
public class TestCEPDemo {
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    static class LoginEvent {
        private Integer userId;
        private String ip;
        private String status;
        private Long timestamp;

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof LoginEvent) {
                LoginEvent loginEvent = (LoginEvent) obj;
                return this.userId == loginEvent.getUserId() && this.ip.equals(loginEvent.ip)
                        && this.status.equals(loginEvent.getStatus()) && this.timestamp == loginEvent.getTimestamp();
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return super.hashCode() + Long.hashCode(timestamp);
        }
    }

    final static List<LoginEvent> loginEventList = Arrays.asList(
            new LoginEvent(1001, "192.168.10.1", "F", 2L),
            new LoginEvent(1001, "192.168.10.2", "F", 3L),
            new LoginEvent(1002, "192.168.10.8", "F", 4L),
            new LoginEvent(1001, "192.168.10.6", "F", 5L),
            new LoginEvent(1002, "192.168.10.8", "F", 7L),
            new LoginEvent(1002, "192.168.10.8", "F", 8L),
            new LoginEvent(1002, "192.168.10.8", "S", 6L),
            new LoginEvent(1003, "192.168.10.8", "F", 6L),
            new LoginEvent(1004, "192.168.10.3", "S", 4L));

    static void test5() throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // 按用户id分组
        DataStream<LoginEvent> loginEventDS = env.fromCollection(loginEventList).assignTimestampsAndWatermarks(
                WatermarkStrategy.<LoginEvent>forBoundedOutOfOrderness(Duration.ofSeconds(10))
                        .withTimestampAssigner((loginEvent, rs) -> loginEvent.getTimestamp()))
                .keyBy(loginEvent -> loginEvent.getUserId());

        // 定义模式
        Pattern<LoginEvent, LoginEvent> loginEventPattern = Pattern.begin(
                Pattern.<LoginEvent>begin("first")
                        .where(new SimpleCondition<LoginEvent>() {

                            @Override
                            public boolean filter(LoginEvent value) throws Exception {
                                return value.getStatus().equals("F");
                            }

                        })

        );

        // 严格连续
        Pattern<LoginEvent, ?> strictLoginEventPattern = loginEventPattern.next(
                Pattern.<LoginEvent>begin("test").where(new SimpleCondition<LoginEvent>() {

                    @Override
                    public boolean filter(LoginEvent value) throws Exception {
                        return false;
                    }

                })).times(3);

        // 松散连续
        Pattern<LoginEvent, ?> relaxedLoginEventPattern = Pattern.begin(
                Pattern.<LoginEvent>begin("r_first")
                        .where(new SimpleCondition<LoginEvent>() {

                            @Override
                            public boolean filter(LoginEvent value) throws Exception {
                                return value.getStatus().equals("F");
                            }

                        })

        ).oneOrMore();

        // 不确定松散连续
        Pattern<LoginEvent, ?> nonDeterminLoginEventPattern = Pattern.begin(
                Pattern.<LoginEvent>begin("n_first")
                        .where(new SimpleCondition<LoginEvent>() {

                            @Override
                            public boolean filter(LoginEvent value) throws Exception {
                                return value.getStatus().equals("F");
                            }

                        })

        ).optional();

        // 将Pattern应用到流上,检测匹配的复杂事件,得到一个PatternStream
        PatternStream<LoginEvent> patternStream = CEP.pattern(loginEventDS, nonDeterminLoginEventPattern);

        // 将匹配到的流选择出来输出
        patternStream
                .select(new PatternSelectFunction<LoginEvent, String>() {
                    @Override
                    public String select(Map<String, List<LoginEvent>> map) throws Exception {
                        return map.get("n_first").toString();
                    }
                })
                .print("输出信息:\n");

        // 控制台输出:

        env.execute();
    }

    public static void main(String[] args) throws Exception {
        test5();
    }
}

具体方法说明详见下图

59、Flink CEP - Flink的复杂事件处理介绍及示例(2)- 模式API_flink hive

4、匹配后跳过策略

对于一个给定的模式,同一个事件可能会分配到多个成功的匹配上。

为了控制一个事件会分配到多少个匹配上,你需要指定跳过策略AfterMatchSkipStrategy。

有五种跳过策略,如下:

  • NO_SKIP: 每个成功的匹配都会被输出。
  • SKIP_TO_NEXT: 丢弃以相同事件开始的所有部分匹配。
  • SKIP_PAST_LAST_EVENT: 丢弃起始在这个匹配的开始和结束之间的所有部分匹配。
  • SKIP_TO_FIRST: 丢弃起始在这个匹配的开始和第一个出现的名称为PatternName事件之间的所有部分匹配。
  • SKIP_TO_LAST: 丢弃起始在这个匹配的开始和最后一个出现的名称为PatternName事件之间的所有部分匹配。

当使用SKIP_TO_FIRST和SKIP_TO_LAST策略时,需要指定一个合法的PatternName。

  • 示例:不同跳过策略的结果
    模式:b+ c
    输入流:b1 b2 b3 c
    结果如下:
  • 示例:NO_SKIP和SKIP_TO_FIRST区别
    模式: (a | b | c) (b | c) c+.greedy d
    输入流:a b c1 c2 c3 d
    结果如下:
  • 示例: NO_SKIP和SKIP_TO_NEXT区别
    模式:a b+
    输入流:a b1 b2 b3
    结果如下:

    想指定要使用的跳过策略,只需要调用下面的方法创建AfterMatchSkipStrategy:

    可以通过调用下面方法将跳过策略应用到模式上:
AfterMatchSkipStrategy skipStrategy = ...;
Pattern.begin("patternName", skipStrategy);

使用SKIP_TO_FIRST/LAST时,有两个选项可以用来处理没有事件可以映射到对应模式名上的情况。 默认情况下会使用NO_SKIP策略,另外一个选项是抛出异常。 可以使用如下的选项:

AfterMatchSkipStrategy.skipToFirst(patternName).throwExceptionOnMiss();

以上,本文介绍了Flink 的类库CEP的单个模式中条件部分、组合模式、模式组和匹配后的跳过策略。