ProcessFunction详解
-
我们之前学习的转换算子是无法访问事件的时间戳信息和水位线信息的。而这在一些应用场景下,极为重要。例如
MapFunction
这样的map
转换算子就无法访问时间戳或者当前事件的事件时间。 -
基于此,
DataStream API
提供了一系列的Low-Level
转换算子。可以访问时间戳、watermark 以及注册定时事件。还可以输出特定的一些事件,例如超时事件等。Process Function
用来构建事件驱动的应用以及实现自定义的业务逻辑(使用之前的window 函数和转换算子无法实现)。例如,Flink SQL
就是使用Process Function
实现的。 -
Flink
提供了八个Process Function
1️⃣:
ProcessFunction
2️⃣:
KeyedProcessFunction
3️⃣:
CoProcessFunction
4️⃣:
ProcessJoinFunction
5️⃣:
BroadcastProcessFunction
6️⃣:
KeyedBroadcastProcessFunction
7️⃣:
ProcessWindowFunction
8️⃣:
ProcessAllWindowFunction
KeyedProcessFunction
函数的组成
-
这里我们重点介绍
KeyedProcessFunction
。 -
KeyedProcessFunction
用来操作KeyedStream
。KeyedProcessFunction
会处理流的每一个元素,输出为 0 个、1 个或者多个元素。所有的Process Function
都继承自RichFunction 接口,所以都有open()
、close()
和getRuntimeContext()
等方法。而KeyedProcessFunction<K, I, O>
(三个参数分别是key的数据类型、输入和输出的数据类型)还额外提供了两个方法:1️⃣:
processElement(I value, Context ctx, Collector<O> out)
, 流中的每一个元素都会调用这个方法,调用结果将会放在Collector
数据类型中输出。Context
可以访问元素的时间戳,元素的key
,以及TimerService
时间服务。Context
还可以将结果输出到别的流(side outputs
)。2️⃣:
onTimer(long timestamp
,OnTimerContext ctx
,Collector<O> out)
是一个回调函数。当之前注册的定时器触发时调用。参数timestamp
为定时器所设定的触发的时间戳。Collector
为输出结果的集合。OnTimerContext
和processElement
的Context
参数一样,提供了上下文的一些信息,例如定时器触发的时间信息(事件时间或者处理时间)。
定时器
-
Context
和OnTimerContext
所持有的TimerService
对象拥有以下方法:1️⃣:
long currentProcessingTime()
返回当前处理时间2️⃣:
long currentWatermark()
返回当前 watermark 的时间戳3️⃣:
void registerProcessingTimeTimer(long timestamp)
会注册当前 key 的processing time 的定时器。当processing time
到达定时时间时,触发 timer。4️⃣:
void registerEventTimeTimer(long timestamp)
会注册当前key
的event time
定时器。当水位线大于等于定时器注册的时间时,触发定时器执行回调函数。5️⃣:
void deleteProcessingTimeTimer(long timestamp)
删除之前注册处理时间定时器。如果没有这个时间戳的定时器,则不执行。6️⃣:
void deleteEventTimeTimer(long timestamp)
删除之前注册的事件时间定时器,如果没有此时间戳的定时器,则不执行。 -
当定时器
timer
触发时,会执行回调函数onTimer()
。注意定时器timer
只能在keyed streams
上面使用。
测试
- 需求:监控温度传感器的温度值,如果温度值在 10 秒钟之内(processing time)连续上升,则报警。
自定义一个JavaBean类
package beans;
/**
* 传感器温度读数的数据类型
*/
public class SenSorReading {
private String id;
private Long timeStamp;
private Double temperature;
public SenSorReading() {
}
public SenSorReading(String id, Long timeStamp, Double temperature) {
this.id = id;
this.timeStamp = timeStamp;
this.temperature = temperature;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getTimeStamp() {
return timeStamp;
}
public void setTimeStamp(Long timeStamp) {
this.timeStamp = timeStamp;
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
@Override
public String toString() {
return "SenSorReading{" +
"id='" + id + '\'' +
", timeStamp=" + timeStamp +
", temperature=" + temperature +
'}';
}
}
程序代码
package processFunction;
import beans.SenSorReading;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
/**
* 检测一段时间内的温度连续上升,输出报警
*/
public class Process_Appaction {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> inputStream = env.socketTextStream("hadoop", 9999);
SingleOutputStreamOperator<SenSorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SenSorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
});
/**
* 测试keyedProcessed
* 先分组然后自定义处理
*/
dataStream.keyBy("id")
.process(new TempConsIncreWarning(10))
.print();
env.execute();
}
public static class TempConsIncreWarning extends KeyedProcessFunction<Tuple, SenSorReading, String> {
//检测时间间隔区域
private Integer interval;
//定义状态,存储上一次输入进来的时间数据
private ValueState<Double> lastTempState;
//定义状态,定时器的时间戳
private ValueState<Long> timerState;
@Override
public void open(Configuration parameters) throws Exception {
lastTempState = getRuntimeContext().getState(new ValueStateDescriptor<Double>("lastTempState", Double.class, Double.MIN_VALUE));
timerState = getRuntimeContext().getState(new ValueStateDescriptor<Long>("timerState", Long.class));
}
public TempConsIncreWarning(Integer interval) {
this.interval = interval;
}
@Override
public void processElement(SenSorReading senSorReading, Context context, Collector<String> collector) throws Exception {
//取出状态
Double lastTemp = lastTempState.value();
Long timerTs = timerState.value();
//如果温度上升并且没有注册定时器的时候就注册十秒的定时器
if (senSorReading.getTemperature() > lastTemp && timerTs == null) {
Long ts = context.timerService().currentProcessingTime() + interval * 1000;
context.timerService().registerProcessingTimeTimer(ts);
timerState.update(ts);
}
//如果温度下降删除定时器
else if (senSorReading.getTemperature() < lastTemp && timerTs != null) {
context.timerService().deleteProcessingTimeTimer(timerTs);
timerState.clear();
}
//更新温度状态
lastTempState.update(senSorReading.getTemperature());
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
out.collect("传感器" + ctx.getCurrentKey().getField(0) + "温度连续" + interval + "s上升!");
timerState.clear();
}
@Override
public void close() throws Exception {
lastTempState.clear();
timerState.clear();
}
}
}
结果展示
侧输出流(SideOutput)
-
大部分的
DataStream API
的算子的输出是单一输出,也就是某种数据类型的流。除了split
算子,可以将一条流分成多条流,这些流的数据类型也都相同。process function
的side outputs
功能可以产生多条流,并且这些流的数据类型可以不一样。一个side output
可以定义为OutputTag[X]
对象,X 是输出流的数据类型。process function
可以通过Context
对象发射一个事件到一个或者多个side outputs
-
将温度以三十度为界限来进行分流
package processFunction;
import beans.SenSorReading;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;
public class Process_SideOutput {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<String> inputStream = env.socketTextStream("hadoop", 9999);
SingleOutputStreamOperator<SenSorReading> dataStream = inputStream.map(line -> {
String[] fields = line.split(",");
return new SenSorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
});
//第一一个output表示低温流
OutputTag<SenSorReading> lowTemp = new OutputTag<SenSorReading>("low-temp") {
};
//自定义测输出流实现分流操作
SingleOutputStreamOperator<SenSorReading> highTemp = dataStream.process(new ProcessFunction<SenSorReading, SenSorReading>() {
@Override
public void processElement(SenSorReading senSorReading, Context context, Collector<SenSorReading> collector) throws Exception {
if (senSorReading.getTemperature() > 30) {
collector.collect(senSorReading);
} else {
context.output(lowTemp, senSorReading);
}
}
});
highTemp.print("high-temp");
highTemp.getSideOutput(lowTemp).print("low-temp");
env.execute();
}
}
结果展示