Flink CEP 简介及使用

开篇

这段时间较忙,很久没有来记录一些学习到的东西。额,也有可能是变懒了。。。 恰巧最近在看flink相关的东西,于是就把相关的东西以及自己的一些理解记录一下。

这篇文章主要是简单介绍一下CEP是干哈的,什么场景下可以用到。接着就来实战一下,用CEP来实现一个需求。最后是对CEP的一些比较常用的API进行介绍。

这里贴一下官网链接和下面example的地址。

Complex event processing for Flink

FlinkCEP 是在flink上面实现的一个可以处理复杂事件的库。它可以从无界事件流中提取你想要的事件或者信息。

FlinkCEP提供了一系列 Pattern API ,我们可以定义自己的 pattern sequences ,用于从原始事件流中匹配和提取事件。

CEP

我们可以这样理解,输入的简单事件,flink计算输出的是提取出来的复杂事件。那么就得知,复杂事件是根据一些规则简单事件流中计算而得来的,这个规则就是我们定义的pattern sequences

由此,假如我们需要检测某些复杂事件,无需写额外的代码去监测输入的原始事件,按需定义pattern sequences 即可。这么讲一通,确实比较抽象,接下来我们看一个例子,进一步体会体会CEP。

example

我们购物时,通常有一些点击浏览商品和加入收藏的一些行为。现在假设每发生上述的行为,都会作为事件输入到Flink中。
现在我们定义规则,对于某个用户,若满足下面其中一个条件,就会被标记为潜在客户,可能会作为后续商品的推广对象。

  1. 先点击浏览商品,然后将商品加入收藏。
  2. 1分钟内点击浏览了商品3次。

好的,我们开始写代码,首先定义Event

public class Event {
    private String name;
    /** 事件类型  0--浏览商品   1---收藏商品*/
    private int type;
}

为了本地测试方便,通过nc命令打开端口向flink模拟输入事件,flink接收到事件之后进行转换

DataStreamSource dataStreamSource = env.socketTextStream("localhost", 9999, "\n");

KeyedStream partitionedInput = dataStreamSource.filter(Objects::nonNull)
                .map(s -> {
                    // 输入的string,逗号分隔,第一个字段为用户名,第二个字段为事件类型
                    String[] strings = s.split(",");
                    if (strings.length != 2) {
                        return null;
                    }
                    Event event = new Event();
                    event.setName(strings[0]);
                    event.setType(Integer.parseInt(strings[1]));
                    return event;
                }).returns(Event.class)
                .keyBy((KeySelector) Event::getName);

接着我们定义两个pattern sequences, patternA 和 patternB代表的规则代码中已经进行注释。

        // 先点击浏览商品,然后将商品加入收藏
        Pattern patternA = Pattern.begin("firstly")
                .where(new SimpleCondition() {
                    @Override
                    public boolean filter(Event event) throws Exception {
                        // 点击商品
                        return event.getType() == 0;
                    }
                })
                .followedBy("and")
                .where(new SimpleCondition() {
                    @Override
                    public boolean filter(Event event) throws Exception {
                        // 将商品加入收藏
                        return event.getType() == 1;
                    }
                });

        // 1分钟内点击浏览了商品3次。
        Pattern patternB = Pattern.begin("start")
                .where(new SimpleCondition() {
                    @Override
                    public boolean filter(Event event) throws Exception {
                        // 浏览商品
                        return event.getType() == 0;
                    }
                })
                .timesOrMore(1)
                .within(Time.minutes(3));

定义好pattern之后,我们就可以利用这两个pattern对输入流进行检测,因为我们有两个pattern sequences, 检测完之后可以对检测出的复杂事件进行union

        // CEP用pattern将输入的时间事件流转化为复杂事件流
        PatternStream patternStreamA = CEP.pattern(partitionedInput, patternA);
        PatternStream patternStreamB = CEP.pattern(partitionedInput, patternB);

        DataStream streamA = processPatternStream(patternStreamA, "收藏商品");
        DataStream streamB = processPatternStream(patternStreamB, "连续浏览商品");

        // 最后两个复杂事件流进行合并
        streamA.union(streamB)
                .print();

完整的代码可以从这里取 代码地址

运行如下

// 输入
Jack,0
Jack,1
//输出
> Jack 成为潜在客户 ,收藏商品

// 输入
Andy,0
Andy,0
Andy,0
// 输出
> Andy 成为潜在客户 ,连续浏览商品

Pattern API 解释

在上面这个example里面,我们使用了一些比较常用的Pattern API,下面我们来对这些API进行认识。

conditions

对于每个pattern需要为其设置条件(condition)来判断到来的简单事件是否被接受,上面的example中,就用到了pattern.where()来进行条件判断。除此之外还提供了, pattern.or()pattern.until()两个方法。
pattern.where()是给当前的pattern定义一个条件。
pattern.or()是给当前的pattern定义一个or条件。
pattern. until()是给当前的pattern定义一个结束循环的条件。

除了为pattern指定条件,还可以设置它的循环次数,下面代码摘自官网,每句代码的作用是显而易见的,就不翻译了。

// expecting 4 occurrences
start.times(4);

// expecting 0 or 4 occurrences
start.times(4).optional();

// expecting 2, 3 or 4 occurrences
start.times(2, 4);

// expecting 2, 3 or 4 occurrences and repeating as many as possible
start.times(2, 4).greedy();

// expecting 0, 2, 3 or 4 occurrences
start.times(2, 4).optional();

// expecting 0, 2, 3 or 4 occurrences and repeating as many as possible
start.times(2, 4).optional().greedy();

// expecting 1 or more occurrences
start.oneOrMore();

// expecting 1 or more occurrences and repeating as many as possible
start.oneOrMore().greedy();

// expecting 0 or more occurrences
start.oneOrMore().optional();

// expecting 0 or more occurrences and repeating as many as possible
start.oneOrMore().optional().greedy();

// expecting 2 or more occurrences
start.timesOrMore(2);

// expecting 2 or more occurrences and repeating as many as possible
start.timesOrMore(2).greedy();

// expecting 0, 2 or more occurrences
start.timesOrMore(2).optional()

// expecting 0, 2 or more occurrences and repeating as many as possible
start.timesOrMore(2).optional().greedy();

Combining Patterns

我们定义多个pattern之后,需要将他们组合起来,如上面我们的example中就:先浏览商品,再点击收藏,我们回忆一下,是这样写的,两个patternfollowedBy进行连接。

        // 先点击浏览商品,然后将商品加入收藏
        Pattern patternA = Pattern.begin("firstly")
                .where(new SimpleCondition() {
                    @Override
                    public boolean filter(Event event) throws Exception {
                        // 点击商品
                        return event.getType() == 0;
                    }
                })
                .followedBy("and")
                .where(new SimpleCondition() {
                    @Override
                    public boolean filter(Event event) throws Exception {
                        // 将商品加入收藏
                        return event.getType() == 1;
                    }
                });

CEP中pattern连接有3中模式,
Strict Contiguity:要求一个event之后必须紧跟下一个符合条件的event,中间不允许有其他事件。
Relaxed Contiguity:和上一种不同的是,该模式允许中间有其他无关的event,会对他们进行忽略。
Non-Deterministic Relaxed Contiguity:非确定性宽松连续性,可以对已经匹配的事件就行忽略,对接下来的事件继续匹配。

上面三种模式分别对应的表达式是:

next()
followedBy()
followedByAny()

对于next()followedBy()比较好理解,那我们来看个关于followedByAny()栗子。
如下 pattern sequence 和input,b+表示一个或者多个b:

// pattern sequence
a (followedByAny) b+ (followedByAny)  c
// input 
 "a", "b1", "d1", "b2", "d2", "b3" "c"

那么,CEP匹配得到的结构有:{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}忽略了已经匹配的事件b2,{a b2 c}{a b2 b3 c}都忽略了已经匹配的事件b1,就是时所谓的Non-Deterministic Relaxed Contiguity

总结

Flink CEP的简单介绍和使用就到这里,通过本文可以对fink cep有一个简单的认识。对于复杂事件的提取,我们只关注于pattern sequence的编写,极大简化了程序的复杂性,提高了可维护性。

你可能感兴趣的:(Flink CEP 简介及使用)