一、处理函数概述
之前所介绍的流处理API、无论是基本的转换、聚合还是更为复杂的窗口操作、其实都是基于DataStream进行转换的。所以统称为DataStreamAPI、这也是Flink编程的核心、而我们知道、为了让代码有更为强大的表现力和易用性、Flink本身提供了多层API、DataStreamAPI只是其中一环。
在更底层、我们可以不定义任何具体的算子(比如Map、filter或者Window)、而只是提炼一个统一的处理"Process"操作、它所有的转换算子的一个概括性表达、可以自定义处理逻辑、所以这一层接口被叫做"处理函数"。
处理函数的功能和使用
我们之前学习的转换算子,一般只是针对某种具体操作来定义的,能够拿到信息比较有限。比如map算子,获取转换之后形式;而像窗口聚合这样的复杂操作,AggregateFunction中除数据外,还可以获取到当前的状态(以累加器Accumulator形式出现)。另外介绍过的富函数类,可以获取运行时上下文的方法getRuntimeContext(),还可以拿到状态、并行度、任务名称之类的运行时信息。但这些算子无法获得访问事件的时间戳、当前的水位线等信息。
处理函数提供了一个定时服务(TimeService),我们可以通过它访问流中的事件(event)、时间戳(timestamp)、水位线(watermark),甚至可以注册“定时事件”。而且处理函数继承了AbstractRichFunction抽象类,故拥有富函数类的所有特性,同样可以访问状态(state)和其他运行时信息。此外,处理函数还可以直接将数据输出到侧输出流(side output)中。故处理函数是最为灵活的处理方法,还可以实现各种自定义的业务逻辑,同时也是整个DataStream API的底层基础。
处理函数的使用与基本的转换操作类似,只需要直接基于DataStream调用.process()方法,传入一个ProcessFunction作为参数:
stream.process(new MyProcessFunction)
ProcessFunction解析
@PublicEvolving
public abstract class ProcessFunction<I, O> extends AbstractRichFunction {
private static final long serialVersionUID = 1L;
public ProcessFunction() {
}
public abstract void processElement(I var1, ProcessFunction<I, O>.Context var2, Collector<O> var3) throws Exception;
public void onTimer(long timestamp, ProcessFunction<I, O>.OnTimerContext ctx, Collector<O> out) throws Exception {
}
public abstract class OnTimerContext extends ProcessFunction<I, O>.Context {
public OnTimerContext() {
super();
}
public abstract TimeDomain timeDomain();
}
public abstract class Context {
public Context() {
}
public abstract Long timestamp();
public abstract TimerService timerService();
public abstract <X> void output(OutputTag<X> var1, X var2);
}
}
1、抽象方法processElement()
用于“处理元素”,定义了处理的核心逻辑,这个方法对于流中的每个元素都会调用一次,参数包括三个:输入数据值value,上下文ctx,以及收集器(Collector)out,处理之后的输出数据通过out来定义
public abstract void processElement(I var1, ProcessFunction<I, O>.Context var2, Collector<O> var3) throws Exception;
2、定时器onTimer()方法
用于定义定时触发的操作,这是一个非常强大,也很有趣的功能。这个方法只有在注册好定时器时才会调用,而定时器是通过定时服务TimeService来注册的。
onTime()传入三个参数:时间戳(timestamp)、上下文(ctx)、收集器(out)。timestamp是指设定好的触发时间,事件时间语义下是水位线。
处理函数是基于事件触发的,水位线就如同插入流中的一条数据一样:处理真正的数据事件调用processFunction()方法,而处理水位线事件调用onTimer()
注意:onTimer()方法只是定时器触发时的操作,而定时器(Timer)真正的设置需要用到上下文ctx中的定时服务。在Flink中,只有“按键分区流”KeyedStream才支持设置定时器的操作
public void onTimer(long timestamp, ProcessFunction<I, O>.OnTimerContext ctx, Collector<O> out) throws Exception {
}
3、处理函数ProcessFunction
public abstract class OnTimerContext extends ProcessFunction<I, O>.Context {
public OnTimerContext() {
super();
}
public abstract TimeDomain timeDomain();
}
4、Context 获取上下文环境
public abstract class Context {
public Context() {
}
public abstract Long timestamp();
public abstract TimerService timerService();
public abstract <X> void output(OutputTag<X> var1, X var2);
}
二、处理函数分类
DataStream在调用一些转换方法之后,有可能生成新的流类型;调用keyBy()得到KeyedStream,进而再调用window()之后得到windowedsTREAM。对于不同类型的流,都可以直接调用process()方法进行自定义处理,这时传入的参数就都可以叫做处理函数。
- ProcessFunction:最基本的处理函数,基于DataStream直接调用process()时作为参数传入
- KeyedProcessFunction:对流按键分区后的处理函数,基于KeyedStream调用process()
- ProcessWindowFunction:开窗之后的处理函数,基于WindowedStream调用process()
- ProcessAllWindowFunction:同样是开窗之后的处理函数,基于AllWindowedStream调用process()
- CoProcessFunction:合并两条流之后的处理函数,基于ConnectedStream调用process()
- ProcessJoinFunction:间隔连接两条流之后的处理函数,基于IntervalJoined调用proecess()
- BroadcastProcessFunction:广播连接流处理函数,基于BroadcastConnectedStream调用process(),这里的广播流是一个未keyBy的普通流DataStream与一个广播流BroadcastStream连接之后的产物
- KeyedBroadcastProcessFunction:按键分区的广播连接流处理函数,同样基于BroadcastConnectedStream调用process(),KeyedStream与广播流连接之后的产物
按键分区处理函数(KeyedProccessFunction)
只有在KeyedStream中才支持使用TimeService设置定时器的操作、所以一般都是在KeyBy之后再去定义分区操作。
定时器和定时服务
KeyedProcessFunction的一个特色,就是可以灵活地使用定时器。
定时器(timers)是处理函数中进行时间相关操作的主要机制,在onTimer()方法中可以实现定时处理的逻辑,在触发之前注册过定时器。
定时服务与当前运行的环境有关,ProcessFunction的上下文(Context)中提供了timerService()方法,可以直接返回TimerService对象:
public abstract TimerService timerService();
TimerService是Flink关于时间和定时器的基础服务接口,包含以下六个方法:
//获取当前的处理时间
long currentProcessingTime();
//获取当前的水位线(事件时间)
long currentWatermark();
//注册事件时间定时器,当处理时间超过time触发
void registerProcessingTimeTimer(long time);
//注册事件时间定时器,当水位线超过time触发
void registerEventTimeTimer(long time);
//删除触发时间为time的处理时间定时器
void deleteProcessingTimeTimer(long time);
//删除触发时间为time的事件时间定时器
void deleteEventTimeTimer(long time);
处理时间定时器
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
DataStreamSource<Event> stream = env.addSource(new ClickSource());
stream.keyBy(data -> data.user)
.process(new KeyedProcessFunction<String, Event,String>() {
@Override
public void processElement(Event event, KeyedProcessFunction<String, Event, String>.Context ctx, Collector<String> collector) throws Exception {
long l = ctx.timerService().currentProcessingTime();
collector.collect(ctx.getCurrentKey() + " 数据到达、到达时间 " + new Timestamp(l));
ctx.timerService().registerProcessingTimeTimer(l + 3 * 1000L);
}
@Override
public void onTimer(long timestamp, KeyedProcessFunction<String, Event, String>.OnTimerContext ctx, Collector<String> out) throws Exception {
out.collect(ctx.getCurrentKey() + " ==========> 定时器触发、触发的时间 " + new Timestamp(timestamp));
}
}).print();
env.execute();
}
事件时间定时器
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(1);
SingleOutputStreamOperator<Event> stream = env.addSource(new ClickSource()).assignTimestampsAndWatermarks(WatermarkStrategy.
<Event>forBoundedOutOfOrderness(Duration.ZERO).withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
@Override
public long extractTimestamp(Event element, long recordTimestamp) {
return element.timeStamp;
}
}));
stream.keyBy(data -> data.user)
.process(new KeyedProcessFunction<String, Event,String>() {
@Override
public void processElement(Event event, KeyedProcessFunction<String, Event, String>.Context ctx, Collector<String> collector) throws Exception {
long l = ctx.timerService().currentWatermark();
collector.collect(ctx.getCurrentKey() + " 数据到达、到达时间 " + new Timestamp(l));
ctx.timerService().registerProcessingTimeTimer(l + 3 * 1000L);
}
@Override
public void onTimer(long timestamp, KeyedProcessFunction<String, Event, String>.OnTimerContext ctx, Collector<String> out) throws Exception {
out.collect(ctx.getCurrentKey() + " ==========> 定时器触发、触发的时间 " + new Timestamp(timestamp));
}
}).print();
env.execute();
}