4. Flink CEP核心API:Pattern API详解、个体模式与组合模式
好,咱们进入正题。Flink CEP 的核心,说白了就是 Pattern API。你想想看,没有 Pattern,你拿什么去定义“什么样的事件序列是异常的”?我刚开始接触 CEP 的时候,觉得这玩意儿就是个高级版的 if-else,后来才发现,它背后是一套完整的有限状态机理论。
今天我就带你把这套 API 拆开揉碎了讲。咱们从最基础的个体模式开始,再到组合模式,一步步把规则定义清楚。
4.1 个体模式(Single Pattern)—— 规则的原子单元
个体模式,就是一条规则里最小的、不可再分的事件匹配条件。比如“登录失败次数超过3次”,这里的“登录失败”就是一个个体模式。
在 Flink CEP 里,个体模式通过 Pattern.begin() 或 Pattern.next() 等方法创建,然后通过 .where() 来定义过滤条件。
核心方法一览:
.where(condition):定义事件必须满足的条件.or(condition):定义“或”关系,满足其一即可.until(condition):定义终止条件(用于循环模式).times(n):指定事件出现的次数.oneOrMore():一次或多次.timesOrMore(n):n次或更多.optional():可选出现.greedy():贪婪匹配
举个例子,假设我们要检测“连续3次登录失败”:
Pattern<LoginEvent, LoginEvent> failPattern = Pattern
.<LoginEvent>begin("first_fail")
.where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event) {
return event.getStatus().equals("FAIL");
}
})
.times(3);
这里 times(3) 表示这个“失败”事件要连续出现3次。嗯,这里要注意:times() 默认是严格连续的,中间不能有其他事件插入。
我的经验: 我在做支付风控时,遇到过一种情况:用户连续输错密码,但中间穿插了一次“忘记密码”操作。如果用 times(3) 严格连续,就会漏掉这种场景。后来我改用 .times(3).allowCombinations(),允许中间跳过一些不相关的事件,才把召回率提上来。
4.2 个体模式的进阶用法
除了简单的 .where(),个体模式还支持更复杂的条件定义。我个人习惯用 IterativeCondition,因为它可以引用之前匹配到的事件。
Pattern<LoginEvent, LoginEvent> failPattern = Pattern
.<LoginEvent>begin("first_fail")
.where(new IterativeCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event, Context<LoginEvent> ctx) throws Exception {
// 获取之前匹配到的第一个失败事件
LoginEvent firstFail = ctx.getEventsForPattern("first_fail").iterator().next();
// 判断两次失败是否在5分钟内
return event.getTimestamp() - firstFail.getTimestamp() < 5 * 60 * 1000;
}
})
.times(3);
你看,通过 ctx.getEventsForPattern(),我们可以拿到之前匹配到的事件,做跨事件的逻辑判断。这在风控场景里太常用了——比如“同一设备在短时间内多次登录失败”。
避坑指南: 我曾经在 IterativeCondition 里不小心用了 ctx.getEventsForPattern() 去获取还没匹配到的事件,结果抛了空指针。记住:你只能引用当前模式之前已经匹配到的模式名称,不能引用后面的。
4.3 组合模式(Combining Patterns)—— 把规则串起来
个体模式是积木块,组合模式就是把这些积木搭建成一个完整的规则。Flink CEP 提供了三种组合方式:
| 方法 | 含义 | 示例场景 |
|---|---|---|
.next() |
严格连续:下一个事件必须紧跟着当前事件 | “登录失败”后紧接着“转账” |
.followedBy() |
宽松连续:后续事件可以出现在任意位置 | “登录失败”之后某时刻发生“转账” |
.followedByAny() |
非确定性宽松连续:允许事件被重复使用 | 同一个事件可以参与多个匹配 |
我画了一张图,帮你理解这三种组合方式的区别:
说白了,.next() 最严格,.followedBy() 最常用,.followedByAny() 最灵活但也最容易产生大量匹配。
4.4 实战:构建一个完整的风控规则
咱们来写一个完整的例子。假设我们要检测“同一设备在5分钟内,先登录失败3次,然后成功登录,最后发起转账”这个风险模式。
Pattern<LoginEvent, LoginEvent> riskPattern = Pattern
.<LoginEvent>begin("fail_attempts")
.where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event) {
return event.getStatus().equals("FAIL");
}
})
.times(3)
.next("success_login")
.where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event) {
return event.getStatus().equals("SUCCESS");
}
})
.followedBy("transfer")
.where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event) {
return event.getType().equals("TRANSFER");
}
})
.within(Time.minutes(5)); // 整个模式在5分钟内完成
关键点解析:
.times(3)后面跟.next(),表示3次失败后必须紧接着成功登录.followedBy()表示成功登录后,转账可以在任意时刻发生.within(Time.minutes(5))是整个模式的超时时间,超过5分钟就不算
你想想看,如果没有 .within(),用户今天登录失败3次,明天成功登录,后天转账,这能算风险吗?显然不能。时间窗口是风控规则里必不可少的约束。
我的经验: 我在做电商风控时,发现很多规则漏报的原因就是时间窗口设得太短。比如“同一IP注册多个账号”,如果窗口只设1小时,那些慢速注册的羊毛党就抓不到。后来我改成6小时窗口,配合 .timesOrMore(5),召回率提升了30%。
4.5 组合模式中的循环与可选
除了基本的组合,Pattern API 还支持一些高级特性:
- 循环模式:
.times(n)、.oneOrMore()、.timesOrMore(n) - 可选模式:
.optional(),表示这个事件可以出现也可以不出现 - 贪婪匹配:
.greedy(),尽可能多地匹配事件
举个例子,检测“用户可能连续失败多次,然后成功登录”:
Pattern<LoginEvent, LoginEvent> pattern = Pattern
.<LoginEvent>begin("fail_attempts")
.where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event) {
return event.getStatus().equals("FAIL");
}
})
.oneOrMore()
.greedy() // 尽可能多地匹配失败事件
.next("success_login")
.where(new SimpleCondition<LoginEvent>() {
@Override
public boolean filter(LoginEvent event) {
return event.getStatus().equals("SUCCESS");
}
});
这里 .greedy() 的作用是:如果有5次失败,它会匹配全部5次,而不是只匹配1次就结束。嗯,这个细节很容易被忽略,但实际影响很大。
避坑指南: 我曾经在 .oneOrMore() 后面忘了加 .greedy(),结果同一个事件序列产生了多个匹配结果(匹配1次、2次、3次...),导致告警风暴。后来我养成了习惯:只要用 oneOrMore 或 timesOrMore,就顺手加上 .greedy(),除非你有特殊需求。
4.6 小结
Pattern API 是 Flink CEP 的基石。个体模式定义“什么样的事件”,组合模式定义“事件之间的顺序关系”。
记住几个要点:
.where()和.or()定义事件条件.next()、.followedBy()、.followedByAny()定义事件顺序.times()、.oneOrMore()定义事件次数.within()定义时间窗口.greedy()控制匹配策略
这些 API 组合起来,几乎能覆盖风控场景里 90% 的规则需求。剩下的 10%,咱们后面会讲到进阶技巧和自定义算子。