Flink 1. 13(三)处理函数

一.概述

在这里插入图片描述

在更底层,我们可以不定义任何具体的算子(比如 map,filter,或者 window),而只是提炼出一个统一的“处理”(process)操作——它是所有转换算子的一个概括性的表达,可以自定义处理逻辑,所以这一层接口就被叫作“处理函数”(process function)。

处理函数提供了一个“定时服务”(TimerService),我们可以通过它访问流中的事件(event)、时间戳(timestamp)、水位线(watermark),甚至可以注册“定时事件”。而且处理函数继承了 AbstractRichFunction 抽象类,所以拥有富函数类的所有特性,同样可以访问状态(state)和其他运行时信息。此外,处理函数还可以直接将数据输出到侧输出流(side output)中。所以,处理函数是最为灵活的处理方法,可以实现各种自定义的业务逻辑;同时也是整个 DataStream API 的底层基础

二.普通处理函数

1.ProcessFunction

ProcessFunction作用

输入IN,自定义输出OUT

ProcessFunction使用

stream.process(new MyProcessFunction())

ProcessFunction源码

public abstract class ProcessFunction<I, O> extends AbstractRichFunction {

    private static final long serialVersionUID = 1L;
	// 核心方法,value输入值,out收集结果,ctx上下文
    public abstract void processElement(I value, Context ctx, Collector<O> out) throws Exception;
	// 定时器到点后处理逻辑
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<O> out) throws Exception {}
    
    public abstract class Context {
		// 数据的时间戳
        public abstract Long timestamp();
		// 查询时间(处理时间和水位线时间)和注册定时器的“定时服务”(TimerService)
        public abstract TimerService timerService();
		// 将数据发送到“侧输出流”
        public abstract <X> void output(OutputTag<X> outputTag, X value);
    }

    public abstract class OnTimerContext extends Context {
        public abstract TimeDomain timeDomain();
    }
}

TimerService源码

@PublicEvolving
public interface TimerService {
    String UNSUPPORTED_REGISTER_TIMER_MSG = "Setting timers is only supported on a keyed streams.
    String UNSUPPORTED_DELETE_TIMER_MSG = "Deleting timers is only supported on a keyed streams.";

  
    long currentProcessingTime();
    
    long currentWatermark();
    
    void registerProcessingTimeTimer(long time);
    
    void registerEventTimeTimer(long time);
    
    void deleteProcessingTimeTimer(long time);
    
    void deleteEventTimeTimer(long time);
}
2.KeyedProcessFunction

KeyedProcessFunction作用

只有在 KeyedStream 中才支持使用 TimerService 设置定时器的操作。所以一般情况下,我们都是先做了 keyBy 分区之后,再去定义处理操作;代码中更加常见的处理函数是 KeyedProcessFunction,最基本的 ProcessFunction 反而出镜率没那么高

输入IN,自定义输出OUT,并设置定时器

KeyedProcessFunction使用

stream.keyBy( t -> t.f0 )
.process(new MyKeyedProcessFunction())

KeyedProcessFunction源码

与ProcessFunction基本一致,只不过context里面多了一个获取当前key的方法

@PublicEvolving
public abstract class KeyedProcessFunction<K, I, O> extends AbstractRichFunction {

    private static final long serialVersionUID = 1L;
    
    public abstract void processElement(I value, Context ctx, Collector<O> out) throws Exception;
    
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<O> out) throws Exception {}
    
    public abstract class Context {
    
        public abstract Long timestamp();
        
        public abstract TimerService timerService();
        
        public abstract <X> void output(OutputTag<X> outputTag, X value);
        
        public abstract K getCurrentKey();
    }
    
    public abstract class OnTimerContext extends Context {
        public abstract TimeDomain timeDomain();
        public abstract K getCurrentKey();
    }
}

定时器案例

1.处理时间定时器

public class ProcessingTimeTimerTest {
    public static void main(String[] args) throws Exception {
        // 1.创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.setParallelism(1);
        env.getConfig().setAutoWatermarkInterval(100); // 100毫秒生成一次水位线

        SingleOutputStreamOperator<Event> streamOperator = env.addSource(new ClickSource())
                // 乱序流的WaterMark生成
                .assignTimestampsAndWatermarks(WatermarkStrategy
                        .<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2)) // 延迟2秒保证数据正确
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override // 时间戳的提取器
                            public long extractTimestamp(Event event, long l) {
                                return event.getTimestamp();
                            }
                        })
                );

        streamOperator.keyBy(Event::getUser)
                .process(new KeyedProcessFunction<String, Event, String>() {
                    @Override
                    public void processElement(Event value, KeyedProcessFunction<String, Event, String>.Context ctx, Collector<String> out) throws Exception {
                        long currTs = ctx.timerService().currentProcessingTime(); // 数据的处理时间
                        out.collect(ctx.getCurrentKey()+"数据到达时间:" + new Timestamp(currTs));
                        // 注册定时器,10秒后触发
                        ctx.timerService().registerProcessingTimeTimer(currTs + 10 * 1000L);
                    }
                    // 10秒后做什么操作 重写onTimer方法
                    @Override
                    public void onTimer(long timestamp, KeyedProcessFunction<String, Event, String>.OnTimerContext ctx, Collector<String> out) throws Exception {
                        super.onTimer(timestamp, ctx, out);
                        out.collect(ctx.getCurrentKey()+"定时器触发时间:"+new Timestamp(timestamp));
                    }
                }).print();

        env.execute();
    }
}

2.事件时间定时器

public class EventTimeTimerTest {
    public static void main(String[] args) throws Exception {
        // 1.创建执行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.setParallelism(1);
        env.getConfig().setAutoWatermarkInterval(100); // 100毫秒生成一次水位线

        SingleOutputStreamOperator<Event> streamOperator = env.addSource(new ClickSource())
                // 乱序流的WaterMark生成
                .assignTimestampsAndWatermarks(WatermarkStrategy
                        .<Event>forBoundedOutOfOrderness(Duration.ofSeconds(2)) // 延迟2秒保证数据正确
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override // 时间戳的提取器
                            public long extractTimestamp(Event event, long l) {
                                return event.getTimestamp();
                            }
                        })
                );

        streamOperator.keyBy(Event::getUser)
                .process(new KeyedProcessFunction<String, Event, String>() {
                    @Override
                    public void processElement(Event value, KeyedProcessFunction<String, Event, String>.Context ctx, Collector<String> out) throws Exception {
                        long currTs = ctx.timestamp(); // 数据自带的时间戳
                        out.collect(ctx.getCurrentKey()+"数据事件时间: " + new Timestamp(currTs) + "- watermark:" + ctx.timerService().currentWatermark());
                        // 注册定时器,10秒后触发
                        ctx.timerService().registerEventTimeTimer(currTs + 10 * 1000L);
                    }
                    // 10秒后做什么操作 重写onTimer方法

                    @Override
                    public void onTimer(long timestamp, KeyedProcessFunction<String, Event, String>.OnTimerContext ctx, Collector<String> out) throws Exception {
                        super.onTimer(timestamp, ctx, out);
                        out.collect(ctx.getCurrentKey()+"定时器触发时间:"+new Timestamp(timestamp));
                    }
                }).print();

        env.execute();

    }
}
3.ProcessWindowFunction

ProcessWindowFunction作用

全窗口函数主要用来收集一个窗口内的所有数据再做处理(按照key分组,一个窗口内的数据具有相同的key)

IN是一个集合,自定义输出OUT

ProcessWindowFunction使用

1.开窗之后的处理函数,主要用作reduce、aggregate的第二个参数

2.基于 WindowedStream 调用.process()时作为参数传入(窗口数据都到齐一起处理)

ProcessWindowFunction源码

@PublicEvolving
public abstract class ProcessWindowFunction<IN, OUT, KEY, W extends Window>
        extends AbstractRichFunction {

  // key:窗口做统计计算基于的键,也就是之前 keyBy 用来分区的字段
  // context:当前窗口进行计算的上下文,它的类型就是 ProcessWindowFunction内部定义的抽象类 Context
  // elements:窗口收集到用来计算的所有数据,这是一个可迭代的集合类型
  // out:用来发送数据输出计算结果的收集器,类型为 Collector
    public abstract void process(
            KEY key, Context context, Iterable<IN> elements, Collector<OUT> out) throws Exception;

    public void clear(Context context) throws Exception {}

    public abstract class Context implements java.io.Serializable {
     	// 主要用于获取窗口起止时间
        public abstract W window();
		// 处理时间
        public abstract long currentProcessingTime();
		// 水位线时间
        public abstract long currentWatermark();
		// 窗口的状态 针对当前key的当前窗口
        public abstract KeyedStateStore windowState();
		// 全局状态 针对当前key的所有窗口
        public abstract KeyedStateStore globalState();
		// 输出到侧输出流
        public abstract <X> void output(OutputTag<X> outputTag, X value);
    }
}

ProcessAllWindowFunction

ProcessAllWindowFunction<IN, OUT, W extends Window>

所以我们会发现,ProcessWindowFunction 中除了.process()方法外,并没有.onTimer()方法,而是多出了一个.clear()方法。从名字就可以看出,这主要是方便我们进行窗口的清理工作。如果我们自定义了窗口状态,那么必须在.clear()方法中进行显式地清除,避免内存溢出

这里有一个问题:没有了定时器,那窗口处理函数就失去了一个最给力的武器,如果我们希望有一些定时操作又该怎么做呢?其实仔细思考会发现,对于窗口而言,它本身的定义就包含了一个触发计算的时间点,其实一般情况下是没有必要再去做定时操作的。如果非要这么干,Flink也提供了另外的途径——使用窗口触发器(Trigger)。在触发器中也有一个TriggerContext,它可以起到类似 TimerService 的作用:获取当前时间、注册和删除定时器,另外还可以获取当前的状态。这样设计无疑会让处理流程更加清晰——定时操作也是一种“触发”,所以我们就 让所有的触发操作归触发器管,而所有处理数据的操作则归窗口函数管

三.合流处理函数

1.CoProcessFunction(connect合流)

CoProcessFunction作用

合并两条类型不同的流后,处理两条流,输出一个类型相同的流

CoProcessFunction使用

stream1.connect(stream2)
.process(CoProcessFunction())

CoProcessFunction源码

@PublicEvolving
public abstract class CoProcessFunction<IN1, IN2, OUT> extends AbstractRichFunction {

    private static final long serialVersionUID = 1L;

    // 流1的处理和输出
    public abstract void processElement1(IN1 value, Context ctx, Collector<OUT> out)
            throws Exception;

    // 流2的处理和输出
    public abstract void processElement2(IN2 value, Context ctx, Collector<OUT> out)
            throws Exception;

  
    public void onTimer(long timestamp, OnTimerContext ctx, Collector<OUT> out) throws Exception {}

   
    public abstract class Context {

        public abstract Long timestamp();

        public abstract TimerService timerService();

        public abstract <X> void output(OutputTag<X> outputTag, X value);
    }

    public abstract class OnTimerContext extends Context {
        public abstract TimeDomain timeDomain();
    }
}

2.ProcessJoinFunction(Interval Join合流)

ProcessJoinFunction作用

间隔连接(interval join)两条流之后的处理函数,基于 IntervalJoined 调用.process()时作为参数传入

ProcessJoinFunction使用

stream1
 .keyBy(<KeySelector>)
 .intervalJoin(stream2.keyBy(<KeySelector>))
 .between(Time.milliseconds(-2000), Time.milliseconds(1000))
 .process (new ProcessJoinFunction<>(){
 });

ProcessJoinFunction源码

public abstract class ProcessJoinFunction<IN1, IN2, OUT> extends AbstractRichFunction {

    private static final long serialVersionUID = -2444626938039012398L;

    
    public abstract void processElement(IN1 left, IN2 right, Context ctx, Collector<OUT> out)
            throws Exception;

  
    public abstract class Context {

        
        public abstract long getLeftTimestamp();

        
        public abstract long getRightTimestamp();

        
        public abstract long getTimestamp();

      
        public abstract <X> void output(OutputTag<X> outputTag, X value);
    }
}

3.BroadcastProcessFunction

BroadcastProcessFunction作用

一个未 keyBy 的普通 DataStream 与一个广播流(BroadcastStream)做连接(conncet)之后,调用.process()时作为参数传入

BroadcastProcessFunction使用

actionStream
.connect(broadcastStream)
.process(new BroadcastProcessFunction());
4.KeyedBroadcastProcessFunction

KeyedBroadcastProcessFunction作用

一个 KeyedStream与广播流(BroadcastStream)做连接之后,调用.process()时作为参数传入,具体的,processBroadcastElement方法可以定义广播状态,并且对广播状态进行值的更新,processElement普通流可以获取广播状态及其值,并根据自己数据做进一步操作

KeyedBroadcastProcessFunction使用

actionStream.keyBy(data -> data.f0)
.connect(broadcastStream)
.process(new KeyedBroadcastProcessFunction());

KeyedBroadcastProcessFunction源码

public abstract class KeyedBroadcastProcessFunction<KS, IN1, IN2, OUT>
        extends BaseBroadcastProcessFunction {

    private static final long serialVersionUID = -2584726797564976453L;

   	// 数据流 ctx可以获取广播状态并且可以根据key获取相应的值  value是新来的值
    public abstract void processElement(
            final IN1 value, final ReadOnlyContext ctx, final Collector<OUT> out) throws Exception;

  
 	// 处理广播流 value是新来的数据 ctx可以定义一个广播状态 将数据保存到广播状态
    public abstract void processBroadcastElement(
            final IN2 value, final Context ctx, final Collector<OUT> out) throws Exception;


    public void onTimer(final long timestamp, final OnTimerContext ctx, final Collector<OUT> out)
            throws Exception {
    }

   
    public abstract class Context extends BaseBroadcastProcessFunction.Context {

        public abstract <VS, S extends State> void applyToKeyedState(
                final StateDescriptor<S, VS> stateDescriptor,
                final KeyedStateFunction<KS, S> function)
                throws Exception;
    }

  
    public abstract class ReadOnlyContext extends BaseBroadcastProcessFunction.ReadOnlyContext {

        public abstract TimerService timerService();
        public abstract KS getCurrentKey();
    }

 
    public abstract class OnTimerContext extends ReadOnlyContext {
        public abstract TimeDomain timeDomain();
        public abstract KS getCurrentKey();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jumanji_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值