目录
一、FlinkCEP是什么
FlinCEP是在Flink之上实现的复杂事件处理(CEP)库。它允许你在无穷无尽的事件流中检测事件模式,让你有机会掌握你的重要内容数据。FlinkCEP是一种基于动态环境中事件流的分析技术,事件在这里通常是有意义的状态变化,通过分析事件间的关系,利用过滤、关联、聚合等技术,根据事件间的时序关系和聚合关系制定检测规则,持续地从事件流中查询出符合要求的事件序列,最终分析得到更复杂的复合事件。
1.目标:从有序的简单事件流中发现一些高阶特征。
2.输入:一个或多个由简单事件构成的事件流。
3.处理:识别简单事件之间的内在联系,多个符合一定规则的简单事件构成复杂事件。
4.输出:满足规则的复杂事件。
二、模式API
模式API可以让你定义想从输入流中抽取的复杂模式序列。
2.1单个模式
一个模式可以是一个单例或者循环模式。单例模式只接受一个事件,循环模式可以接受多个事件。在模式匹配表达式中,模式"a b+ c? d"(或者"a",后面跟着一个或者多个"b",再往后可选择的跟着一个"c",最后跟着一个"d"),a,c?,和 d都是单例模式,b+是一个循环模式。默认情况下,模式都是单例的,你可以通过使用量词把它们转换成循环模式。每个模式可以有一个或者多个条件来决定它接受哪些事件。
1. 量词
在FlinkCEP中,你可以通过这些方法指定循环模式:pattern.oneOrMore(),指定期望一个给定事件出现一次或者多次的模式(例如前面提到的b+模式); pattern.times(#ofTimes),指定期望一个给定事件出现特定次数的模式,例如出现4次a; pattern.times(#fromTimes, #toTimes),指定期望一个给定事件出现次数在一个最小值和最大值中间的模式,比如出现2-4次a。下面以start为名字的模式对象,解释所涉及到的量词意义。
// 期望出现4次
start.times(4);
// 期望出现0或者4次
start.times(4).optional();
// 期望出现2、3或者4次
start.times(2, 4);
// 期望出现2、3或者4次,并且尽可能的重复次数多
start.times(2, 4).greedy();
// 期望出现0、2、3或者4次
start.times(2, 4).optional();
// 期望出现0、2、3或者4次,并且尽可能的重复次数多
start.times(2, 4).optional().greedy();
// 期望出现1到多次
start.oneOrMore();
// 期望出现1到多次,并且尽可能的重复次数多
start.oneOrMore().greedy();
// 期望出现0到多次
start.oneOrMore().optional();
// 期望出现0到多次,并且尽可能的重复次数多
start.oneOrMore().optional().greedy();
// 期望出现2到多次
start.timesOrMore(2);
// 期望出现2到多次,并且尽可能的重复次数多
start.timesOrMore(2).greedy();
// 期望出现0、2或多次
start.timesOrMore(2).optional();
// 期望出现0、2或多次,并且尽可能的重复次数多
start.timesOrMore(2).optional().greedy();
2. 条件
对每个模式你可以指定一个条件来决定一个进来的事件是否被接受进入这个模式,例如,它的value字段应该大于5,或者大于前面接受的事件的平均值。指定判断事件属性的条件可以通过pattern.where()、pattern.or()或者pattern.until()方法。
迭代条件: 这是最普遍的条件类型。使用它可以指定一个基于前面已经被接受的事件的属性,或者它们的一个子集的统计数据来决定是否接受时间序列的条件。
下面是一个迭代条件的代码,它接受"middle"模式下一个事件的名称开头是"foo",并且前面已经匹配到的事件加上这个事件的价格小于5.0。迭代条件非常强大,尤其是跟循环模式结合使用时。
middle.oneOrMore()
.subtype(SubEvent.class)
.where(new IterativeCondition<SubEvent>() {
@Override
public boolean filter(SubEvent value, Context<SubEvent> ctx) throws Exception {
if (!value.getName().startsWith("foo")) {
return false;
}
double sum = value.getPrice();
for (Event event : ctx.getEventsForPattern("middle")) {
sum += event.getPrice();
}
return Double.compare(sum, 5.0) < 0;
}
});
简单条件: 这种类型的条件扩展了前面提到的IterativeCondition类,它决定是否接受一个事件只取决于事件自身的属性。
start.where(new SimpleCondition<Event>() {
@Override
public boolean filter(Event value) {
return value.getName().startsWith("foo");
}
});
组合条件: 如上所示,你可以把subtype条件和其他的条件结合起来使用。这适用于任何条件,你可以通过依次调用where()来组合条件。最终的结果是每个单一条件的结果的逻辑and。如果想使用or来组合条件,你可以像下面这样使用or()方法。
pattern.where(new SimpleCondition<Event>() {
@Override
public boolean filter(Event value) {
return ...; // 一些判断条件
}
}).or(new SimpleCondition<Event>() {
@Override
public boolean filter(Event value) {
return ...; // 一些判断条件
}
});
停止条件: 如果使用循环模式(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}由于停止条件没有被输出。
Pattern<WaterSensor, WaterSensor> end = getSomePattern().next("end")
.where(new SimpleCondition<WaterSensor>() {
@Override
public boolean filter(WaterSensor waterSensor) throws Exception {
return S2.equals(waterSensor.getId());
}
}).oneOrMore().until(new SimpleCondition<WaterSensor>() {
/*
until(condition) ;为循环模式指定一个停止条件。
意思是满足了给定的条件的事件出现后,就不会再有事件被接受进入模式了。
只适用于和oneOrMore()同时使用。NOTE: 在基于事件的条件中,它可用于清理对应模式的状态。
*/
@Override
public boolean filter(WaterSensor waterSensor) throws Exception {
return S1.equals(waterSensor.getId());
}
});
2.2组合模式
严格连续(严格紧邻)
期望所有匹配的事件严格的一个接一个出现,中间没有任何不匹配的事件。
Pattern<WaterSensor,
WaterSensor> pattern = Pattern
.<WaterSensor>begin("start")
.where(new SimpleCondition<WaterSensor>() {
@Override
public boolean filter(WaterSensor value) throws Exception {
return "sensor_1".equals(value.getId());
}
})
.next("end")
.where(new SimpleCondition<WaterSensor>() {
@Override
public boolean filter(WaterSensor value) throws Exception {
return "sensor_2".equals(value.getId());
}
});
注意:notNext 如果不想后面直接连着一个特定事件
松散连续
忽略匹配的事件之间的不匹配的事件。
Pattern<WaterSensor,
WaterSensor> pattern = Pattern
.<WaterSensor>begin("start")
.where(new SimpleCondition<WaterSensor>() {
@Override
public boolean filter(WaterSensor value) throws Exception {
return "sensor_1".equals(value.getId());
}
})
.followedBy("end")
.where(new SimpleCondition<WaterSensor>() {
@Override
public boolean filter(WaterSensor value) throws Exception {
return "sensor_2".equals(value.getId());
}
});
注意:notFollowBy 如果不想一个特定事件发生在两个事件之间的任何地方。(notFollowBy不能位于事件的最后)
非确定的松散连续
更进一步的松散连续,允许忽略掉一些匹配事件的附加匹配。当且仅当数据为a,c,b,b时,对于followedBy模式而言命中的为{a,b},对于followedByAny而言会有两次命中{a,b},{a,b}
Pattern<WaterSensor,
WaterSensor> pattern = Pattern
.<WaterSensor>begin("start")
.where(new SimpleCondition<WaterSensor>() {
@Override
public boolean filter(WaterSensor value) throws Exception {
return "sensor_1".equals(value.getId());
}
})
.followedByAny("end")
.where(new SimpleCondition<WaterSensor>() {
@Override
public boolean filter(WaterSensor value) throws Exception {
return "sensor_2".equals(value.getId());
}
});
对于循环模式(例如oneOrMore()和times())),默认是松散连续。如果想使用严格连续,你需要使用consecutive()方法明确指定,如果想使用不确定松散连续,你可以使用allowCombinations()方法。
2.3模式组
也可以定义一个模式序列作为begin,followedBy,followedByAny和next的条件。这个模式序列在逻辑上会被当作匹配的条件,并且返回一个GroupPattern,可以在GroupPattern上使用oneOrMore(),times(#ofTimes), times(#fromTimes, #toTimes),optional(),consecutive(),allowCombinations()。
Pattern<Event, ?> start = Pattern.begin(
Pattern.<Event>begin("start").where(...).followedBy("start_middle").where(...)
);
// 严格连续
Pattern<Event, ?> strict = start.next(
Pattern.<Event>begin("next_start").where(...).followedBy("next_middle").where(...)
).times(3);
// 松散连续
Pattern<Event, ?> relaxed = start.followedBy(
Pattern.<Event>begin("followedby_start").where(...).followedBy("followedby_middle").where(...)
).oneOrMore();
// 不确定松散连续
Pattern<Event, ?> nonDetermin = start.followedByAny(
Pattern.<Event>begin("followedbyany_start").where(...).followedBy("followedbyany_middle").where(...)
).optional();
模式操作 | 描述 |
---|---|
begin(#name) | 定义一个开始的模式: ```java Pattern start = Pattern.begin("start"); ``` |
begin(#pattern_sequence) | 定义一个开始的模式: ```java Pattern start = Pattern.begin( Pattern.begin("start").where(...).followedBy("middle").where(...) ); ``` |
next(#name) | 增加一个新的模式。匹配的事件必须是直接跟在前面匹配到的事件后面(严格连续): ```java Pattern next = start.next("middle"); ``` |
next(#pattern_sequence) | 增加一个新的模式。匹配的事件序列必须是直接跟在前面匹配到的事件后面(严格连续): ```java Pattern next = start.next( Pattern.begin("start").where(...).followedBy("middle").where(...) ); ``` |
followedBy(#name) | 增加一个新的模式。可以有其他事件出现在匹配的事件和之前匹配到的事件中间(松散连续): ```java Pattern followedBy = start.followedBy("middle"); ``` |
followedBy(#pattern_sequence) | 增加一个新的模式。可以有其他事件出现在匹配的事件序列和之前匹配到的事件中间(松散连续): ```java Pattern followedBy = start.followedBy( Pattern.begin("start").where(...).followedBy("middle").where(...) ); ``` |
followedByAny(#name) | 增加一个新的模式。可以有其他事件出现在匹配的事件和之前匹配到的事件中间, 每个可选的匹配事件都会作为可选的匹配结果输出(不确定的松散连续): ```java Pattern followedByAny = start.followedByAny("middle"); ``` |
followedByAny(#pattern_sequence) | 增加一个新的模式。可以有其他事件出现在匹配的事件序列和之前匹配到的事件中间, 每个可选的匹配事件序列都会作为可选的匹配结果输出(不确定的松散连续): ```java Pattern followedByAny = start.followedByAny( Pattern.begin("start").where(...).followedBy("middle").where(...) ); ``` |
notNext() | 增加一个新的否定模式。匹配的(否定)事件必须直接跟在前面匹配到的事件之后(严格连续)来丢弃这些部分匹配: ```java Pattern notNext = start.notNext("not"); ``` |
notFollowedBy() | 增加一个新的否定模式。即使有其他事件在匹配的(否定)事件和之前匹配的事件之间发生, 部分匹配的事件序列也会被丢弃(松散连续): ```java Pattern notFollowedBy = start.notFollowedBy("not"); ``` |
within(time) | 定义匹配模式的事件序列出现的最大时间间隔。如果未完成的事件序列超过了这个事件,就会被丢弃: ```java pattern.within(Time.seconds(10)); ``` |
2.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 | b1 b2 b3 c b2 b3 c b3 c | 找到匹配b1 b2 b3 c之后,不会丢弃任何结果。 |
SKIP_TO_NEXT | b1 b2 b3 c b2 b3 c b3 c | 找到匹配b1 b2 b3 c之后,不会丢弃任何结果,因为没有以b1开始的其他匹配。 |
SKIP_PAST_LAST_EVENT | b1 b2 b3 c | 找到匹配b1 b2 b3 c之后,会丢弃其他所有的部分匹配。 |
SKIP_TO_FIRST[b] | b1 b2 b3 c b2 b3 c b3 c | 找到匹配b1 b2 b3 c之后,会尝试丢弃所有在b1之前开始的部分匹配,但没有这样的匹配,所以没有任何匹配被丢弃。 |
SKIP_TO_LAST[b] | b1 b2 b3 c b3 c | 找到匹配b1 b2 b3 c之后,会尝试丢弃所有在b3之前开始的部分匹配,有一个这样的b2 b3 c被丢弃。 |
在看另外一个例子来说明NO_SKIP和SKIP_TO_FIRST之间的差别:模式: (a | b | c) (b | c) c+.greedy d,输入:a b c1 c2 c3 d,结果将会是:
跳过策略 | 结果 | 描述 |
---|---|---|
NO_SKIP | a b c1 c2 c3 d b c1 c2 c3 d c1 c2 c3 d | 找到匹配a b c1 c2 c3 d之后,不会丢弃任何结果。 |
SKIP_TO_FIRST[c*] | a b c1 c2 c3 d c1 c2 c3 d | 找到匹配a b c1 c2 c3 d之后,会丢弃所有在c1之前开始的部分匹配,有一个这样的b c1 c2 c3 d被丢弃。 |
为了更好的理解NO_SKIP和SKIP_TO_NEXT之间的差别,看下面的例子:模式:a b+,输入:a b1 b2 b3,结果将会是:
跳过策略 | 结果 | 描述 |
---|---|---|
NO_SKIP | a b1 a b1 b2 a b1 b2 b3 | 找到匹配a b1之后,不会丢弃任何结果。 |
SKIP_TO_NEXT | a b1 | 找到匹配a b1之后,会丢弃所有以a开始的部分匹配。这意味着不会产生a b1 b2和a b1 b2 b3了。 |
想指定要使用的跳过策略,只需要调用下面的方法创建AfterMatchSkipStrategy:
方法 | 描述 |
---|---|
AfterMatchSkipStrategy.noSkip() | 创建NO_SKIP策略 |
AfterMatchSkipStrategy.skipToNext() | 创建SKIP_TO_NEXT策略 |
AfterMatchSkipStrategy.skipPastLastEvent() | 创建SKIP_PAST_LAST_EVENT策略 |
AfterMatchSkipStrategy.skipToFirst(patternName) | 创建引用模式名称为patternName的SKIP_TO_FIRST策略 |
AfterMatchSkipStrategy.skipToLast(patternName) | 创建引用模式名称为patternName的SKIP_TO_LAST策略 |
可以通过调用下面方法将跳过策略应用到模式上:
AfterMatchSkipStrategy skipStrategy=AfterMatchSkipStrategy.noSkip();
Pattern.begin("super",skipStrategy);
总结
因为模式API中知识点有点多,咱们梳理一下。组合模式=多个单个模式。单个模式=(单例模式||循环模式=(单例模式+量词)),模式组=模式序列在逻辑上被当成条件,并给后面的模式使用。模式API是整个CEP编程的核心,搞定模式API可以很好的帮助我们快速实现分控,营销,监控等功能。
单例模式
Pattern.<LoginEvent>begin("fail").where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent loginEvent) throws Exception {
return FAIL.equals(loginEvent.getStatus());
}
})
循环模式
Pattern.<LoginEvent>begin("fail").where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent loginEvent) throws Exception {
return FAIL.equals(loginEvent.getStatus());
}
}).timesOrMore(2)
组合模式
Pattern.<LoginEvent>begin("fail").where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent loginEvent) throws Exception {
return FAIL.equals(loginEvent.getStatus());
}
}).timesOrMore(2)
.next("end").where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent loginEvent) throws Exception {
return true;
}
})
//严格连续
.until(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent loginEvent) throws Exception {
return SUCCESS.equals(loginEvent.getStatus());
}
}).within(Time.seconds(2));
模式组
Pattern<Event, ?> start = Pattern.begin(
Pattern.<Event>begin("start").where(...).followedBy("start_middle").where(...)
);
// 严格连续
Pattern<Event, ?> strict = start.next(
Pattern.<Event>begin("next_start").where(...).followedBy("next_middle").where(...)
).times(3);