1、介绍
FlinkCEP是在Flink之上实现的复杂事件处理(CEP)库。它允许您在无穷无尽的事件流中检测事件模式,使您有机会掌握数据中重要的内容。通常会用来做一些用户操作APP的日志风控策略等多种复杂事件,下面详细以用户连续10s内登陆失败超过3次告警为需求,进行全面讲解。
1.1、整体需求数据详解图
2、官方案例
官方代码案例如下:
DataStream<Event> input = ...
Pattern<Event, ?> pattern = Pattern.<Event>begin("start").where(
new SimpleCondition<Event>() {
@Override
public boolean filter(Event event) {
return event.getId() == 42;
}
}
).next("middle").subtype(SubEvent.class).where(
new SimpleCondition<SubEvent>() {
@Override
public boolean filter(SubEvent subEvent) {
return subEvent.getVolume() >= 10.0;
}
}
).followedBy("end").where(
new SimpleCondition<Event>() {
@Override
public boolean filter(Event event) {
return event.getName().equals("end");
}
}
);
PatternStream<Event> patternStream = CEP.pattern(input, pattern);
DataStream<Alert> result = patternStream.process(
new PatternProcessFunction<Event, Alert>() {
@Override
public void processMatch(
Map<String, List<Event>> pattern,
Context ctx,
Collector<Alert> out) throws Exception {
out.collect(createAlertFrom(pattern));
}
});
2.1、官方案例总结
CEP编程步骤
a)定义模式序列
Pattern.<Class>begin("patternName").API...
基本都是按照如上的套路来新建自定义一个模式规则
后续的可以跟的API可以在官方中查看学习
Event Processing (CEP) | Apache Flink
b)将模式序列作用到流上
CEP.pattern(inputDataStream,pattern)
CEP.pattern()是固定格式写法,
其中第一个参数,表示需要具体作用的流;
第二个参数,表示具体的自定义的模式。
c)提取匹配上的数据和输出
由b)生成的流用process API来进行数据处理输出,继承PatternProcessFunction,重写processMatch(Map<String, List<Event>> pattern,Context ctx,Collector<Alert> out)方法,
第一个参数,表示具体匹配上的数据,其中Map的key就是a)步骤中定义的"patternName"名称,value就是该名称具体对应规则匹配上的数据集;
第二个参数,表示没匹配上的数据侧输出流
第三个参数,表示具体该函数处理完,需要对外输出的内容收集。
3、需求案例详解
下面就以从Socket中模拟读取用户操作日志数据,来进行数据CEP匹配数据输出。
以如下代码把读进来的数据进行数据打平成JavaBean。该章节的讲解以代码段进行,后续章节会把demo代码全部贴出来。
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
/**
* 设置成1,是为了能够触发watermark来计算
*/
env.setParallelism(1);
DataStreamSource<String> socketTextStream = env.socketTextStream("localhost", 8888);
SingleOutputStreamOperator<UserLoginLog> dataStream = socketTextStream.flatMap(new MyFlatMapFunction())
.assignTimestampsAndWatermarks(
WatermarkStrategy.<UserLoginLog>forBoundedOutOfOrderness(Duration.ofSeconds(1))
.withTimestampAssigner((SerializableTimestampAssigner<UserLoginLog>) (element, recordTimestamp) -> element.getLoginTime())
);
3.1、使用begin.where.next.where.next
/**
* 10s钟之内连续3次登陆失败的才输出,强制连续
*/
Pattern<UserLoginLog, UserLoginLog> wherePatternOne = Pattern.<UserLoginLog>begin("start").where(new SimpleCondition<UserLoginLog>() {
@Override
public boolean filter(UserLoginLog value) throws Exception {
return 1 == value.getLoginStatus();
}
}).next("second").where(new IterativeCondition<UserLoginLog>() {
@Override
public boolean filter(UserLoginLog value, Context<UserLoginLog> ctx) throws Exception {
return 1 == value.getLoginStatus();
}
}).next("third").where(new SimpleCondition<UserLoginLog>() {
@Override
public boolean filter(UserLoginLog value) throws Exception {
return 1 == value.getLoginStatus();
}
}).within(Time.seconds(10));
如上根据设置判断登陆状态是否为失败开始计数,连续第二条,第三条如果也同样为失败的话,就会输出
//如下日志数据输入,最终将输出loginId为:11111、11112、11113、11116、11117、11121
{"loginId":11111,"loginTime":1645177352000,"loginStatus":1,"userName":"aaron"}
{"loginId":11112,"loginTime":1645177353000,"loginStatus":1,"userName":"aaron"}
{"loginId":11113,"loginTim