【Flink系列】处理函数简介

一、处理函数概述

之前所介绍的流处理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();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值