在flink中,转换算子是无法访问事件的时间戳信息和水位线信息的。

所以!

  flink提供了一些列的Low-Level转换算法,他们可以访问时间戳,watermark以及注册定时器。

总结:

ProcessFunction可以被认为是一种提供了对状态和定时器访问的FlatMapFunction,没接收到一个数据流都会进行处理,可以通过访问时间戳来进行设置定时器等操作。

  flink提供了8个ProcessFunction:

    ProcessFunction dataStream

    KeyedProcessFunction 用于KeyedStream,keyBy之后的流处理

    CoProcessFunction 用于connect连接的流

    ProcessJoinFunction 用于join流操作

    BroadcastProcessFunction 用于广播

    KeyedBroadcastProcessFunction keyBy之后的广播

    ProcessWindowFunction 窗口增量聚合

    ProcessAllWindowFunction 全窗口聚合
 

1、KeyedBroadcastProcessFunction

  能看出来,KeyedBroadcastProcessFunction是在keyby之后进行处理的ProcessFunction。

  使用模型:

     参数有三个:K(key)、I(输入)、O(输出)

flink addSource过时 flink keyedprocessfunction_flink addSource过时

 查看源码:

/**
*处理输入流中的一个元素。 
*value:传入的数据
*ctx:上下文,KeyedBroadcastProcessFunction内部实现了Context,可以访问定时器的时间戳以及当前元素*的时间戳。
*out:输出收集器
*/
public abstract void processElement(I value, Context ctx, Collector<O> out) throws Exception;

/**
*定时器
*timestamp:触发定时器的时间戳
*/
public void onTimer(long timestamp, OnTimerContext ctx, Collector<O> out) throws Exception {}

2、CoProcessFunction

  之前介绍过CoGroup,是flink用来连接流的一种方式。

  主要工作流程

  

flink addSource过时 flink keyedprocessfunction_apache_02

   源码分析:

    CoProcessFunction与KeyedBroadcastProcessFunction底层都是实现AbstractRichFunction,不过里边是分成了两个流的分别处理。处理流1和流2

flink addSource过时 flink keyedprocessfunction_时间戳_03

   使用demo:

     1、首先接收两个流并keyby

// 流1 要先按照id分组
DataStreamSource<String> sourceStream1 = env.addSource(consumer);
KeyedStream<String, Tuple> stream1 = sourceStream1.keyBy(1);
// 流2 要先按照id分组
DataStreamSource<String> sourceStream2 = env.addSource(consumer);
KeyedStream<String, Tuple> stream2 = sourceStream1.keyBy(1);

    2、流进行连接并使用

      

flink addSource过时 flink keyedprocessfunction_flink_04

     3、处理是对两个流分别处理,可以通过注册状态将两个流进行交互。

3、ProcessJoinFunction

  ProcessJoinFunction 用来处理join连接之后的数据

  当然跟CoProcessFunction还是有一定区别的,我们看源码

  

flink addSource过时 flink keyedprocessfunction_apache_05

  它的processElement里边可以直接访问两个流的数据。

4、 BroadcastProcessFunction

  之前介绍过Broadcast State:

  Broadcast State 是 Flink 1.5 引入的新特性。在开发过程中,如果遇到需要下发/广播配置、规则等低吞吐事件流到下游所有 task 时,就可以使用 Broadcast State 特性。下游的 task 接收这些配置、规则并保存为 BroadcastState, 将这些配置应用到另一个数据流的计算中 。

  简单来说就是正常处理的数据流A与配置流B,B里边是一些动态变化的的规则,比如我们要将流A中大于5的输出出来,后边想改成8,我们只要再向B流传入8,将这个阈值当做状态保存,就可以动态进行规则变化。
  它的源码中,上边是正常数据流,下边是配置规则流,所以我们一般都是 流A connect 流B

  

flink addSource过时 flink keyedprocessfunction_flink addSource过时_06

   具体代码参考之前广播状态文章。

5、KeyedBroadcastProcessFunction

   KeyedBroadcastProcessFunction 跟 BroadcastProcessFunction类似,不过多了一步keyby。

   看一下源码:

   

flink addSource过时 flink keyedprocessfunction_flink_07

  传入多了一个key.

  具体使用demo:

dataStream.keyBy(0).connect(broadcastStream).process(new KeyedBroadcastProcessFunction<String, Tuple3<String, Integer, Long>, Map<String, Object>, Tuple2<String,Integer>>()

 6、ProcessWindowFunction

    ProcessWindowFunction也是增量聚合函数,类似于AggregateFunction,但是他内部可以访问上下文等。

    ProcessWindowFunction也会等窗口关闭再对窗口内的数据进行计算

    简单实例:

import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimeCharacteristic;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.RichSourceFunction;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.util.Collector;
import java.util.Random;
import java.util.concurrent.TimeUnit;

public class WindowFuncMain {

    public static void main(String[] args) throws Exception {

        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
        env.setParallelism(1);

        DataStream<Tuple2<String, Integer>> source = env.addSource(new RichSourceFunction<Tuple2<String, Integer>>() {
            private boolean isRunning = true;
            //班级
            private String[] cla = {"A", "B", "C", "D"};
            private Random random = new Random();
            //满分
            final Integer scores = 100;

            public void run(SourceContext<Tuple2<String, Integer>> ctx) throws Exception {
                while (isRunning) {
                    TimeUnit.SECONDS.sleep(1);
                    //随机选择一个班级
                    String cl = cla[random.nextInt(cla.length)];
                    Integer score = random.nextInt(scores);
                    Tuple2 tuple2 = new Tuple2(cl, score);
                    System.out.println("发送班级为:" + cl + "分数为:" + score);
                    ctx.collect(tuple2);
                }
            }

            public void cancel() {
                isRunning = false;
            }
        });

        //使用ProcessWindowFunction
        DataStream<String> resultStream = source.keyBy(value -> value.f0)
                .timeWindow(Time.seconds(10))
                .process(new MyProcessWindowFunc());

        resultStream.print();
        env.execute();

    }


    public static class MyProcessWindowFunc extends ProcessWindowFunction<Tuple2<String, Integer>, String, String, TimeWindow> {

        //这里的ValueState存了所有的状态,更新或者添加删除其实也是根据key去取的
        private ValueState<Integer> valueState;
        private int size = 0;

        @Override
        public void open(Configuration parameters) throws Exception {
            valueState = getRuntimeContext().getState(new ValueStateDescriptor<Integer>("classScore", Integer.class, 0));
        }

        @Override
        public void process(String s, Context context, Iterable<Tuple2<String, Integer>> elements, Collector<String> out) throws Exception {

            for (Tuple2<String, Integer> tuple2 : elements) {
                valueState.update(valueState.value() + tuple2.f1);
                size = size + 1;
            }
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append(s).append(":").append(valueState.value()).append(" ").append(size);
            out.collect(stringBuffer.toString());
            size = 0;
        }

        @Override
        public void clear(Context context) throws Exception {
            valueState.clear();
        }
    }

}

7、ProcessAllWindowFunction

  全窗口函数:ProcessAllWindowFunction 

  简单理解一下就是可以对窗口内所有元素进行操作,我们查看一下源码:

  

flink addSource过时 flink keyedprocessfunction_时间戳_08

可以看到没有传入参数key,所以 ProcessAllWindowFunction 的用法跟ProcessWindowFunction的区别就是不需要进行keyby。