前言
  • 我们之前学习的转换算子是无法访问事件的时间戳信息和水位线信息的。而这在一些应用场景下,极为重要。例如MapFunction这样的map转换算子就无法访问时间戳或者当前事件的事件时间。

  • 基于此,DataStream API 提供了一系列的Low-Level 转换算子。可以访问时间戳、watermark 以及注册定时事件。还可以输出特定的一些事件,例如超时事件等。Process Function 用来构建事件驱动的应用以及实现自定义的业务逻辑(使用之前的window 函数和转换算子无法实现)。例如,Flink SQL 就是使用 Process Function 实现的。

  • Flink提供了八个Process Function

    1️⃣ProcessFunction

    2️⃣KeyedProcessFunction

    3️⃣CoProcessFunction

    4️⃣ProcessJoinFunction

    5️⃣BroadcastProcessFunction

    6️⃣KeyedBroadcastProcessFunction

    7️⃣ProcessWindowFunction

    8️⃣ProcessAllWindowFunction

跳转顶部


KeyedProcessFunction

函数的组成

  • 这里我们重点介绍 KeyedProcessFunction

  • KeyedProcessFunction 用来操作 KeyedStreamKeyedProcessFunction 会处理流的每一个元素,输出为 0 个、1 个或者多个元素。所有的 Process Function 都继承自RichFunction 接口,所以都有 open()close()getRuntimeContext()等方法。而KeyedProcessFunction<K, I, O>(三个参数分别是key的数据类型、输入和输出的数据类型)还额外提供了两个方法:

    1️⃣processElement(I value, Context ctx, Collector<O> out), 流中的每一个元素都会调用这个方法,调用结果将会放在 Collector数据类型中输出。Context 可以访问元素的时间戳,元素的 key,以及 TimerService 时间服务。Context 还可以将结果输出到别的流(side outputs)。

    2️⃣onTimer(long timestamp, OnTimerContext ctx, Collector<O> out) 是一个回调函数。当之前注册的定时器触发时调用。参数 timestamp 为定时器所设定的触发的时间戳。Collector 为输出结果的集合。OnTimerContextprocessElementContext参数一样,提供了上下文的一些信息,例如定时器触发的时间信息(事件时间或者处理时间)。

跳转顶部


定时器

  • ContextOnTimerContext所持有的 TimerService对象拥有以下方法:

    1️⃣long currentProcessingTime()返回当前处理时间

    2️⃣long currentWatermark()返回当前 watermark 的时间戳

    3️⃣void registerProcessingTimeTimer(long timestamp) 会注册当前 key 的processing time 的定时器。当 processing time 到达定时时间时,触发 timer。

    4️⃣void registerEventTimeTimer(long timestamp) 会注册当前keyevent time 定时器。当水位线大于等于定时器注册的时间时,触发定时器执行回调函数。

    5️⃣void deleteProcessingTimeTimer(long timestamp) 删除之前注册处理时间定时器。如果没有这个时间戳的定时器,则不执行。

    6️⃣void deleteEventTimeTimer(long timestamp) 删除之前注册的事件时间定时器,如果没有此时间戳的定时器,则不执行。

  • 当定时器timer触发时,会执行回调函数onTimer()。注意定时器timer只能在keyed streams上面使用。


测试

  • 需求:监控温度传感器的温度值,如果温度值在 10 秒钟之内(processing time)连续上升,则报警。

自定义一个JavaBean类

package beans;

/**
 * 传感器温度读数的数据类型
 */
public class SenSorReading {
    private String id;
    private Long timeStamp;
    private Double temperature;

    public SenSorReading() {
    }

    public SenSorReading(String id, Long timeStamp, Double temperature) {
        this.id = id;
        this.timeStamp = timeStamp;
        this.temperature = temperature;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Long getTimeStamp() {
        return timeStamp;
    }

    public void setTimeStamp(Long timeStamp) {
        this.timeStamp = timeStamp;
    }

    public Double getTemperature() {
        return temperature;
    }

    public void setTemperature(Double temperature) {
        this.temperature = temperature;
    }

    @Override
    public String toString() {
        return "SenSorReading{" +
                "id='" + id + '\'' +
                ", timeStamp=" + timeStamp +
                ", temperature=" + temperature +
                '}';
    }
}

程序代码

package processFunction;

import beans.SenSorReading;
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;

/**
 * 检测一段时间内的温度连续上升,输出报警
 */
public class Process_Appaction {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        DataStreamSource<String> inputStream = env.socketTextStream("hadoop", 9999);

        SingleOutputStreamOperator<SenSorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SenSorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });

        /**
         * 测试keyedProcessed
         * 先分组然后自定义处理
         */
        dataStream.keyBy("id")
                .process(new TempConsIncreWarning(10))
                .print();
        env.execute();
    }

    public static class TempConsIncreWarning extends KeyedProcessFunction<Tuple, SenSorReading, String> {
        //检测时间间隔区域
        private Integer interval;
        //定义状态,存储上一次输入进来的时间数据
        private ValueState<Double> lastTempState;
        //定义状态,定时器的时间戳
        private ValueState<Long> timerState;

        @Override
        public void open(Configuration parameters) throws Exception {
            lastTempState = getRuntimeContext().getState(new ValueStateDescriptor<Double>("lastTempState", Double.class, Double.MIN_VALUE));
            timerState = getRuntimeContext().getState(new ValueStateDescriptor<Long>("timerState", Long.class));
        }

        public TempConsIncreWarning(Integer interval) {
            this.interval = interval;
        }

        @Override
        public void processElement(SenSorReading senSorReading, Context context, Collector<String> collector) throws Exception {
            //取出状态
            Double lastTemp = lastTempState.value();
            Long timerTs = timerState.value();

            //如果温度上升并且没有注册定时器的时候就注册十秒的定时器
            if (senSorReading.getTemperature() > lastTemp && timerTs == null) {
                Long ts = context.timerService().currentProcessingTime() + interval * 1000;
                context.timerService().registerProcessingTimeTimer(ts);
                timerState.update(ts);
            }
            //如果温度下降删除定时器
            else if (senSorReading.getTemperature() < lastTemp && timerTs != null) {
                context.timerService().deleteProcessingTimeTimer(timerTs);
                timerState.clear();
            }

            //更新温度状态
            lastTempState.update(senSorReading.getTemperature());
        }

        @Override
        public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
            out.collect("传感器" + ctx.getCurrentKey().getField(0) + "温度连续" + interval + "s上升!");
            timerState.clear();
        }

        @Override
        public void close() throws Exception {
            lastTempState.clear();
            timerState.clear();
        }
    }
}

结果展示
【Flink】ProcessFunction详解_大数据

跳转顶部


侧输出流(SideOutput)
  • 大部分的DataStream API的算子的输出是单一输出,也就是某种数据类型的流。除了split算子,可以将一条流分成多条流,这些流的数据类型也都相同。process functionside outputs功能可以产生多条流,并且这些流的数据类型可以不一样。一个side output可以定义为OutputTag[X]对象,X 是输出流的数据类型。process function可以通过Context对象发射一个事件到一个或者多个side outputs

  • 将温度以三十度为界限来进行分流

package processFunction;

import beans.SenSorReading;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;

public class Process_SideOutput {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        DataStreamSource<String> inputStream = env.socketTextStream("hadoop", 9999);

        SingleOutputStreamOperator<SenSorReading> dataStream = inputStream.map(line -> {
            String[] fields = line.split(",");
            return new SenSorReading(fields[0], new Long(fields[1]), new Double(fields[2]));
        });
        //第一一个output表示低温流
        OutputTag<SenSorReading> lowTemp = new OutputTag<SenSorReading>("low-temp") {
        };

        //自定义测输出流实现分流操作
        SingleOutputStreamOperator<SenSorReading> highTemp = dataStream.process(new ProcessFunction<SenSorReading, SenSorReading>() {
            @Override
            public void processElement(SenSorReading senSorReading, Context context, Collector<SenSorReading> collector) throws Exception {
                if (senSorReading.getTemperature() > 30) {
                    collector.collect(senSorReading);
                } else {
                    context.output(lowTemp, senSorReading);
                }
            }
        });
        highTemp.print("high-temp");
        highTemp.getSideOutput(lowTemp).print("low-temp");
        env.execute();
    }
}

结果展示
【Flink】ProcessFunction详解_big data_02

跳转顶部