ProcessFunction API
ProcessFunction API其实是Flink底层的(low-level)API。
StreamData除了为我们提供了我们之前介绍过的高层转换算子还为我们提供了底层转换算子。与高层转换算子不同,通过这些底层转换算子我问可以访问数据的时间戳、watermark以及注册定时事件等等。
Flink为我们提供了8中process function:
- ProcessFunction
- KeyedProcessFunction
- CoProcessFunction
- ProcessJoinFunction
- BroadcastProcessFunction
- KeyedBroadcastProcessFunction
- ProcessWindowFunction
- ProcessAllWindowFunction
下面我们重点就KeyedProcessFunction做一下介绍,其他的算子用法基本类似。
KeyedProcessFunction
调用方式:
inputData.flatMap(new MySpliter())
.process(new KeyedProcessFunction<String, Sensor, String>() {})
所有的processfunction都是通过 .process(new 底层函数) 的方式调用的,而且所有的Process Function都实现了Rich Function接口,所以都有open和close方法。除此之外KeyedProcessFunction还提供了两个方法,分别是:
- processElement(Sensor sensor, Context context, Collector collector):每一条数据都会走这个方法,然后将结果放到collector中输出。而context包含上下文信息,包括当前数据的时间戳、key以及时间服务,还可以将数据放到侧输出流。
- onTimer(long timestamp, OnTimerContext ctx, Collector out):当定时器触发时调用。timestamp是定时器的触发时间戳,ctx上下文信息,out收集输出信息
TimeService和Times(定时器)
Context和OnTimerContext的TimeService对象都拥有以下方法:
//返回当前处理时间
long currentProcessingTime();
//返回当前水位线
long currentWatermark();
//注册当前key的process time定时器,process time到达定时时间时触发timer
void registerProcessingTimeTimer(long var1);
//注册当前key的event time定时器,当watermark大于等于定时时间时触发timer
void registerEventTimeTimer(long var1);
//删除指定时间戳的process time定时器
void deleteProcessingTimeTimer(long var1);
//删除指定时间戳的event time定时器
void deleteEventTimeTimer(long var1);
这么说可能太抽象,咱们举个栗子:当传感器温度值在1秒钟这内持续升高则发出报警信息。
上代码
inputData.flatMap(new MySpliter())
.keyBy(new GetMyKey())
.process(new KeyedProcessFunction<String, Sensor, String>() {
//上一条数据的温度值
private ValueState<Double> lastTemp = getRuntimeContext().getState(new ValueStateDescriptor<>("lastTemp", Types.DOUBLE));
//定时时间戳
private ValueState<Long> currentTime = getRuntimeContext().getState(new ValueStateDescriptor<Long>("timer",Types.LONG))
@Override
public void processElement(Sensor sensor, Context context, Collector<String> collector) throws Exception {
Double preTemp = lastTemp.value();
lastTemp.update(sensor.getTemperature());
Long currentTimeTamp = currentTime.value();
if(preTemp == 0.0 || sensor.getTemperature()<= preTemp){
//如果是第一条数据或者当前温度值小于等于上一条数据的温度值,清除定时器
context.timerService().deleteProcessingTimeTimer(currentTimeTamp);
currentTime.clear();
}else if(sensor.getTemperature() > preTemp && currentTimeTamp == 0){
//如果当前温度大于上一条数据温度并且定时时间戳为零时,注册定时器
Long timerTs = context.timerService().currentProcessingTime()+ 1000;
context.timerService().registerProcessingTimeTimer(timerTs);
}
}
@Override
public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
out.collect("id为"+ctx.getCurrentKey()+"传感器温度值已经连续1秒上升了。");
currentTime.clear();
}
})
侧输出流
Flink大部分的算子都是说出一条特定类型的流。Split可以输出多条流,但是数据类型也是相同的,而且Flink已经不推荐使用split了。process function的侧输出流(slide outputs)可以输出多条不同类型的流。废话少说,上代码:
DataStreamSource<String> inputData = env.addSource(new MyDataSource());
SingleOutputStreamOperator<Sensor> process = inputData.flatMap(new MySpliter()).process(new FreezingMonitor());
process.getSideOutput(new OutputTag<Sensor>("freezing-alarms")).print();
process.print();
private static class FreezingMonitor extends ProcessFunction<Sensor, Sensor> {
// 定义一个侧输出标签
OutputTag<String> freezingAlarmOutput = new OutputTag<String>("freezing-alarms");
@Override
public void processElement(Sensor r, Context ctx, Collector<Sensor> out) throws Exception {
// 温度在32F以下时,输出警告信息
if (r.getTemperature() < 32.0) {
ctx.output(freezingAlarmOutput, "Freezing Alarm for "+r.getId());
}
// 所有数据直接常规输出到主流
out.collect(r);
}
}