flink1.12.x WaterMark 迟到数据丢失和收集(OutputTag、allowedLateness)

watermark缺点

由 flink1.12.x watermark 水印、水位线 可知下面缺点:

收到 数据5 时,由于 数据5的事件时间 09:21:26 - 延迟 5 秒 >= 窗口结束时间 09:21:20,会触发 窗口1 计算

由于 data3 迟到太多,仍然会被 丢弃

allowedLateness、OutputTag

由下面文章可知:

  1. flink1.12.x WaterMark 迟到数据丢失和收集OutputTag

  2. flink1.12.x Watermark 迟到数据 allowedLateness()

allowedLateness 之后,即:收到 事件时间是 8000 的数据,8000ms - 2s(watermark) - 3s(allowedLateness) > 5s(窗口结束时间),那么 窗口1关闭,再接收到 窗口1的数据,就放入到 OutputTag 侧流中

API 说明

创建 OuputTag

OutputTag<Tuple3<String, Double, Long>> outputTag = new OutputTag("late_data", Types.TUPLE(Types.STRING,Types.DOUBLE,Types.LONG));

api 调用

详见 链接

获取侧输出流

DataStream<Tuple3<String, Double, Long>> lateDataDS = maxSOSO.getSideOutput(outputTag);

例子

package allowerdLateness_outputtag;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.api.common.typeinfo.Types;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.windowing.ProcessWindowFunction;
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows;
import org.apache.flink.streaming.api.windowing.time.Time;
import org.apache.flink.streaming.api.windowing.windows.TimeWindow;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;
import org.apache.kafka.clients.consumer.ConsumerConfig;

import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Iterator;
import java.util.Properties;


public class Std3 {

    public static void main(String[] args) throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        // 自定义watermark
        WatermarkStrategy<Tuple3<String, Double, Long>> watermarkStrategy = WatermarkStrategy.
                <Tuple3<String, Double, Long>>forBoundedOutOfOrderness(Duration.ofSeconds(2))
                .withTimestampAssigner(new SerializableTimestampAssigner<Tuple3<String, Double, Long>>() {
                    @Override
                    public long extractTimestamp(Tuple3<String, Double, Long> element, long recordTimestamp) {
                        return element.f2;
                    }
                });

        /*
         ----------------------关键----------------------
         创建 OutputTag 对象,收集 由于迟到太多丢弃的数据
         */
        OutputTag<Tuple3<String, Double, Long>> outputTag = new OutputTag("late_data", Types.TUPLE(Types.STRING,Types.DOUBLE,Types.LONG));
        // 处理
        SingleOutputStreamOperator<Object> soso = env.addSource(buildKafka())
                .flatMap(new FlatMapFunImpl())
                .assignTimestampsAndWatermarks(watermarkStrategy)
                .keyBy(value -> value.f0)
                .window(TumblingEventTimeWindows.of(Time.seconds(5)))
                .allowedLateness(Time.seconds(1))
                .sideOutputLateData(outputTag)
                .process(new ProcessWindowFunImpl());

        DataStream<Tuple3<String, Double, Long>> sideOutput = soso.getSideOutput(outputTag);
        sideOutput.print("sideOutput::");
//        soso.print("res:");
        System.out.println(env.getExecutionPlan());
        env.execute("myjob");
    }

    private static class ProcessWindowFunImpl extends ProcessWindowFunction<Tuple3<String,Double,Long>, Object, String, TimeWindow> {


        @Override
        public void process(String key, Context context, Iterable<Tuple3<String, Double, Long>> input, Collector<Object> out) throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            TimeWindow window = context.window();

            String startTime = sdf.format(window.getStart());
            String endTime = sdf.format(window.getEnd());
            String content = String.format("\tRichWindowFunImpl--开始计算,key:%s,窗口开始时间:%s,窗口结束时间:%s,watermark:%s",
                    key,
                    startTime,
                    endTime,
                    context.currentWatermark());
            System.out.println(content);

            Iterator<Tuple3<String,Double, Long>> iterator = input.iterator();
            while(iterator.hasNext()){
                Tuple3<String,Double, Long> temp = iterator.next();
                System.out.println("\tRichWindowFunImpl--遍历--"+temp);
            }

            System.out.println("\tRichWindowFunImpl--计算结束,key:"+key);

            out.collect("1");// 这里为了演示,收集数据不重要,所以收集1

        }
    }
    private static FlinkKafkaConsumer<String> buildKafka(){
        Properties pro = new Properties();
        pro.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop1:9092,hadoop2:9092,hadoop3:9092");
        pro.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "consumer-group");
        pro.setProperty(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");

        // 内置连接器,创建FlinkKafka消费者,需要指定主题、反序列化、kafka配置
        FlinkKafkaConsumer<String> fkc = new FlinkKafkaConsumer<>("kafka2flink", new SimpleStringSchema(), pro);
        return fkc;
    }
    static class FlatMapFunImpl implements FlatMapFunction<String, Tuple3<String,Double,Long>> {

        @Override
        public void flatMap(String value, Collector<Tuple3<String,Double,Long>> out) throws Exception {
            ObjectMapper om = new ObjectMapper();
            JsonNode jsonNode = om.readTree(value);
            JsonNode idJN = jsonNode.get("id");
            JsonNode tempJN = jsonNode.get("temp");
            JsonNode timeJN = jsonNode.get("time");
            String id = idJN.asText();
            Double temp = tempJN.asDouble();
            long timeStamp = timeJN.asLong();

            Tuple3<String, Double, Long> ret = Tuple3.of(id, temp, timeStamp);
            System.out.println("接收数据:"+ret);
            out.collect(ret);
        }
    }

}

测试数据

{"id":"1","temp":20,"time":1000}
{"id":"2","temp":21,"time":4000}
{"id":"1","temp":22,"time":2000}
{"id":"1","temp":30,"time":3000}
{"id":"2","temp":40,"time":6000}
{"id":"1","temp":21,"time":7000}
{"id":"1","temp":100,"time":2000}
{"id":"2","temp":100,"time":4000}
{"id":"1","temp":21,"time":8000}
{"id":"1","temp":1000,"time":2000}
{"id":"2","temp":1000,"time":4000}

执行结果

参考:
https://blog.csdn.net/lmalds/article/details/55259718


原文出处:/show_1IX4864EnNeh.html