flink CEP
Apache Flink提供FlinkCEP库,该库提供用于执行复杂事件处理的API。该库由以下核心组件组成:
事件流
模式定义
模式检测
警报生成
FlinkCEP在Flink的名为DataStream的流API上工作。程序员需要从事件流中定义要检测的模式,然后Flink的CEP引擎检测该模式并采取适当的操作,例如生成警报。
为了开始,我们需要添加以下Maven依赖项:
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-cep- scala_2.10 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-cep-scala_2.11</artifactId>
<version>1.1.4</version>
</dependency>
事件流
CEP的一个非常重要的组成部分是它的输入事件流。在前面的章节中,我们已经看到了数据流API的详细信息。现在让我们使用这些知识来实现CEP。我们需要做的第一件事就是为事件定义JavaPOJO。假设我们需要监视温度传感器事件流。
首先定义一个抽象类,然后扩展这个类。
注意
在定义事件POJO时,我们需要确保实现hashCode ( )
和equals ()方法,因为在比较事件时,compile将使用它们。
下面的代码片段演示了这一点。首先,我们编写一个抽象类,如下所示:
package com.demo.chapter05;
public abstract class MonitoringEvent {
private String machineName;
public String getMachineName() {
return machineName;
}
public void setMachineName(String machineName) {
this.machineName = machineName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((machineName == null) ? 0 :
machineName.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MonitoringEvent other = (MonitoringEvent) obj;
if (machineName == null) {
if (other.machineName != null) return false;
} else if (!machineName.equals(other.machineName)) return false;
return true;
}
public MonitoringEvent(String machineName) {
super();
this.machineName = machineName;
}
}
然后,我们为实际温度事件创建POJO :
package com.demo.chapter05;
public class TemperatureEvent extends MonitoringEvent {
public TemperatureEvent(String machineName) {
super(machineName);
}
private double temperature;
public double getTemperature() {
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
long temp;
temp = Double.doubleToLongBits(temperature);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
TemperatureEvent other = (TemperatureEvent) obj;
if (Double.doubleToLongBits(temperature) != Double.doubleToLongBits(other.temperature))
return false;
return true;
}
public TemperatureEvent(String machineName, double temperature) {
super(machineName);
this.temperature = temperature;
}
@Override
public String toString() {
return "TemperatureEvent [getTemperature()=" + getTemperature() + ", getMachineName()=" + getMachineName()
+ "]";
}
}
现在,我们可以如下定义事件源:在Java中:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<TemperatureEvent> inputEventStream = env.fromElements(new TemperatureEvent("xyz", 22.0),
new TemperatureEvent("xyz", 20.1), new TemperatureEvent("xyz", 21.1), new TemperatureEvent("xyz", 22.2),
new TemperatureEvent("xyz", 22.1), new TemperatureEvent("xyz",
22.3), new TemperatureEvent("xyz", 22.1),
new TemperatureEvent("xyz", 22.4), new TemperatureEvent("xyz",
22.7),
new TemperatureEvent("xyz", 27.0));
In Scala:
StreamExecutionEnvironment env=StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<TemperatureEvent> inputEventStream=env.fromElements(new TemperatureEvent("xyz",22.0),
new TemperatureEvent("xyz",20.1),new TemperatureEvent("xyz",21.1),new TemperatureEvent("xyz",22.2),
new TemperatureEvent("xyz",22.1),new TemperatureEvent("xyz",
22.3),new TemperatureEvent("xyz",22.1),
new TemperatureEvent("xyz",22.4),new TemperatureEvent("xyz",
22.7),
new TemperatureEvent("xyz",27.0));
模式API
模式API允许您非常容易地定义复杂的事件模式。每个模式由多个状态组成。要从一个状态到另一个状态,通常我们需要定义条件。条件可以是连续性或过滤掉事件。
让我们尝试详细了解每个模式操作。
Begin开始
初始状态可以定义如下:
In Java:
Pattern<Event, ?> start = Pattern.begin(“start”);
In Scala:
val start : Pattern[Event, _] = Pattern.begin(“start”)
Filter过滤器
我们还可以指定初始状态的筛选条件:
In Java:
start.where(new FilterFunction<Event>() {
@Override
public boolean filter(Event value) {
return ... // condition
}
});
In Scala:
start.where(event => … /* condition */)
Subtype图表类型
我们还可以使用subtype ( )方法根据事件的子类型筛选出事件:
In Java:
start.subtype(SubEvent.class).where(new FilterFunction<SubEvent>() { @Override
public boolean filter(SubEvent value) {
return ... // condition }
});
In Scala:
start.subtype(classOf[SubEvent]).where(subEvent => … /* condition */)
OR
模式API还允许我们一起定义多个条件。我们可以使用OR和AND运算符。
In Java:
pattern.where(new FilterFunction<Event>() {
@Override
public boolean filter(Event value) {
return ... // condition
}
}).or(new FilterFunction<Event>() {
@Override
public boolean filter(Event value) {
return ... // or condition
}
});
In Scala:
pattern.where(event => … /* condition /).or(event => … / or condition */)
Continuity连续性
如前所述,我们并不总是需要过滤掉事件。总有一些模式需要连续性而不是过滤器。
连续性可以分为两种类型-严格连续性和非严格连续性。
Strict continuity严格连续性
严格的连续性需要两个事件才能直接成功,这意味着两个事件之间不应有其他事件。此模式可由next ()定义。
In Java:
Pattern<Event, ?> strictNext = start.next(“middle”);
In Scala:
val strictNext: Pattern[Event, _] = start.next(“middle”)
Non-strict continuity
非严格连续性可以表述为其他事件之间允许的特定
两个事件。此模式可由followedBy ()定义。
In Java:
Pattern<Event, ?> nonStrictNext = start.followedBy(“middle”);
In Scala:
val nonStrictNext : Pattern[Event, _] = start.followedBy(“middle”)
Within
patternAPI还允许我们根据时间间隔进行模式匹配。我们可以如下定义基于时间的时间约束。
In Java:
next.within(Time.seconds(30));
In Scala:
next.within(Time.seconds(10))
Detecting patterns检测模式
要针对事件流检测模式,我们需要通过模式运行该流。pattern ( )返回patternstream。
下面的代码片段展示了如何检测模式。首先,定义模式以检查温度值在10秒钟内是否大于26.0度。
In Java:
Pattern<TemperatureEvent,?> warningPattern = Pattern.<TemperatureEvent> begin("first").subtype(TemperatureEvent.class).
where(new FilterFunction<TemperatureEvent>() {
@Override
public boolean filter(TemperatureEvent value) throws Exception {
if (value.getTemperature() >= 26.0){
return true;
}
return false;
}
}).within(Time.seconds(10));
PatternStream<TemperatureEvent> pts = CEP.pattern(inputEventStream,warningPattern);
In Scala:
val env: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val input = // data
val pattern: Pattern[TempEvent, _] = Pattern.begin("start").where(event => event.temp >= 26.0)
val patternStream: PatternStream[TempEvent] = CEP.pattern(input, pattern)
Selecting from patterns从模式中选择
一旦模式流可用,我们需要从中选择模式,然后基于它采取适当的操作。我们可以使用select或flatselect方法从阵列中选择数据。
Select挑选
select方法需要PatternSelectionFunction实现。它具有为每个事件序列调用的select方法。select方法接收匹配事件的字符串/事件对的映射。字符串由状态的名称定义。select方法只返回一个结果。
为了收集结果,我们需要定义输出POJO。在我们的示例中,假设我们需要生成警报作为输出。然后,我们需要定义POJO,如下所示:
package com.demo.chapter05;
public class Alert {
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Alert(String message) {
super();
this.message = message;
}
@Override
public String toString() {
return "Alert{" +
"message='" + message + '\'' +
'}';
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Alert other = (Alert) obj;
if (message == null) {
if (other.message != null) return false;
} else if (!message.equals(other.message)) return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((message == null) ? 0 : message.hashCode());
return result;
}
}
接下来我们定义select函数。
In Java:
class MyPatternSelectFunction<IN, OUT> implements PatternSelectFunction<IN, OUT> {
@Override
public OUT select(Map<String, IN> pattern) {
IN startEvent = pattern.get("start");
IN endEvent = pattern.get("end");
return new OUT(startEvent, endEvent);
}
}
In Scala:
def selectFn(pattern :mutable.Map[String, IN]):OUT={
val startEvent=pattern.get("start").get
val endEvent=pattern.get("end").get
OUT(startEvent,endEvent)
}
flatSelect
flatselect方法与select方法类似。两者之间的唯一区别是flatselect可以返回任意数量的结果。flatselect方法具有用于输出元素的附加收集器参数。
下面的示例演示如何使用flatselect方法。
In Java:
class MyPatternFlatSelectFunction<IN, OUT> implements PatternFlatSelectFunction<IN, OUT> {
@Override
public void select(Map<String, IN> pattern, Collector<OUT> collector) {
IN startEvent = pattern.get("start");
IN endEvent = pattern.get("end");
for (int i = 0; i < startEvent.getValue(); i++) {
collector.collect(new OUT(startEvent, endEvent));
}
}
}
In Scala:
def flatSelectFn(pattern: mutable.Map[String, IN], collector: Collector[OUT] ) = {
val startEvent = pattern.get ("start").get val endEvent = pattern.get ("end").get
for (i <- 0 to startEvent.getValue) {
collector.collect (OUT (startEvent, endEvent) )
}
}
Handling timed-out partial patterns处理超时的部分模式
有时,如果我们用时间限制模式,我们可能会错过某些事件。事件可能会因为超过长度而被丢弃。为了对超时事件采取操作,select和flatselect方法允许超时
处理程序。为每个超时事件模式调用此处理程序。
在这种情况下,select方法包含两个参数: PatternSelectFunction和patterntimeoutfunction。超时函数的返回类型可以与selectpattern函数不同。超时事件和select事件也包装在类中。右和左
下面的代码片段展示了我们在实践中的工作方式。
In Java:
PatternStream<Event> patternStream = CEP.pattern(input, pattern);
DataStream<Either<TimeoutEvent, ComplexEvent>> result = patternStream.select(
new PatternTimeoutFunction<Event, TimeoutEvent>() {...},
new PatternSelectFunction<Event, ComplexEvent>() {...} );
DataStream<Either<TimeoutEvent, ComplexEvent>> flatResult = patternStream.flatSelect(
new PatternFlatTimeoutFunction<Event, TimeoutEvent>() {...},
new PatternFlatSelectFunction<Event, ComplexEvent>() {...} );
In Scala, the select API:
val patternStream: PatternStream[Event] = CEP.pattern(input, pattern)
DataStream[Either[TimeoutEvent, ComplexEvent]] result = patternStream.select{
(pattern: mutable.Map[String, Event], timestamp: Long) => TimeoutEvent()
}{
pattern: mutable.Map[String, Event] => ComplexEvent() }
flat select API是与收集器一起调用的,因为它可以发出任意数量的事件:
val patternStream: PatternStream[Event] = CEP.pattern(input, pattern)
DataStream[Either[TimeoutEvent, ComplexEvent]] result = patternStream.flatSelect{
(pattern: mutable.Map[String, Event], timestamp: Long, out: Collector[TimeoutEvent]) =>
out.collect(TimeoutEvent())
}{
(pattern: mutable.Map[String, Event], out:
Collector[ComplexEvent]) =>
}
Use case - complex event processing on a temperature sensor温度传感器上的用例复杂事件处理
在前面的部分中,我们了解了FlinkCEP引擎提供的各种特性。现在是了解我们如何在实际解决方案中使用它的时候了。为此,假设我们在一家生产一些产品的机械公司工作。在产品工厂中,需要不断监视某些机器。工厂已经设置了传感器,这些传感器在给定的时间内持续发送机器的温度。
现在,我们将设置一个系统,该系统持续监视温度值,并在温度超过某个值时生成警报。
我们可以使用以下体系结构:
传感器 --> kafka --> flink Streams -->CEP 模式匹配 --> 消息
在这里,我们将使用Kafka从传感器收集事件。为了编写Java应用程序,我们首先需要创建Maven项目并添加以下依赖项:
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink-cep- scala_2.11 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-cep-scala_2.11</artifactId>
<version>1.1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink- streaming-java_2.11 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-java_2.11</artifactId>
<version>1.1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.flink/flink- streaming-scala_2.11 -->
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_2.11</artifactId>
<version>1.1.4</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka-0.9_2.11</artifactId>
<version>1.1.4</version>
</dependency>
接下来,我们需要做以下事情来使用Kafka。
首先,我们需要定义一个自定义的Kafka反序列化程序。这将从Kafka主题中读取字节并将其转换为temperatureevent。下面是执行此操作的代码。
事件反序列化模式
EventDeserializationSchema.java:
package com.demo.chapter05;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.apache.flink.api.common.typeinfo.TypeInformation;
import org.apache.flink.api.java.typeutils.TypeExtractor;
import org.apache.flink.streaming.util.serialization.DeserializationSchema;
public class EventDeserializationSchema implements DeserializationSchema<TemperatureEvent> {
public TypeInformation<TemperatureEvent> getProducedType() {
return TypeExtractor.getForClass(TemperatureEvent.class);
}
public TemperatureEvent deserialize(byte[] arg0) throws IOException {
String str = new String(arg0, StandardCharsets.UTF_8);
String[] parts = str.split("=");
return new TemperatureEvent(parts[0], Double.parseDouble(parts[1]));
}
public boolean isEndOfStream(TemperatureEvent arg0) {
return false;
}
}
接下来,我们在Kafka中创建了名为temperature的主题:
bin/kafka-topics --create --zookeeper localhost:2181 --replication-
factor 1 --partitions 1 --topic temperature
现在我们转到Java代码,它将在脱机流中侦听这些事件:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
Properties properties = new Properties();
properties.setProperty("bootstrap.servers", "localhost:9092");
properties.setProperty("group.id", "test");
DataStream<TemperatureEvent> inputEventStream = env.addSource(
new FlinkKafkaConsumer09<TemperatureEvent>("temperature", new
EventDeserializationSchema(), properties));
接下来,我们将定义模式以检查温度是否大于26.0摄氏度
10秒内:
Pattern<TemperatureEvent, ?> warningPattern = Pattern.<TemperatureEvent>begin("first").subtype(TemperatureEvent.class).where(new FilterFunction<TemperatureEvent>() {
private static final long serialVersionUID = 1L;
public boolean filter(TemperatureEvent value) {
if (value.getTemperature() >= 26.0) {
return true;
}
return false;
}
}).within(Time.seconds(10));
接下来,将此模式与事件流匹配并选择事件。我们还会将警报消息添加到结果流中,如下所示:
DataStream<Alert> patternStream = CEP.pattern(inputEventStream, warningPattern)
.select(new PatternSelectFunction<TemperatureEvent, Alert>() {
private static final long serialVersionUID = 1L;
public Alert select(Map<String, TemperatureEvent> event) throws Exception {
return new Alert("Temperature Rise Detected:" + event.get("first").getTemperature()
+ " on machine name:" + event.get("first").getMachineName());
}
});
为了了解生成了哪些警报,我们将打印结果:
patternStream.print();
我们执行流:
env.execute(“CEP on Temperature Sensor”);
现在我们都准备好执行应用程序了。当我们在Kafka主题中获得消息时,CEP将继续执行。
实际执行如下所示。下面是我们如何提供示例输入:
xyz=21.0
xyz=30.0
LogShaft=29.3
Boiler=23.1
Boiler=24.2
Boiler=27.0
Boiler=29.0
以下是示例输出的外观:
我们还可以配置邮件客户端,并使用一些外部web挂接发送电子邮件或messenger通知。
注意
应用程序的代码可以在GitHub上找到:
https://github.com/deshpandetanmay/mastering-flink