深入浅出Flink-第四章(waterMark)

1 需求背景

需求描述:每隔5秒,计算最近10秒单词出现的次数。
在这里插入图片描述

1.1 TimeWindow实现

/**
 * 每隔5秒计算最近10秒单词出现的次数
 */
public class TimeWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> dataStream = env.socketTextStream("localhost", 8888);
        SingleOutputStreamOperator<Tuple2<String, Integer>> result = dataStream.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out) throws Exception {
                String[] fields = line.split(",");
                for (String word : fields) {
                    out.collect(new Tuple2<>(word, 1));
                }
            }
        }).keyBy(0)
                .timeWindow(Time.seconds(10), Time.seconds(5))
                .sum(1);

        result.print().setParallelism(1);

        env.execute("TimeWindowWordCount");

    }
}

1.2 ProcessWindowFunction

/**
 * 每隔5秒计算最近10秒单词出现的次数
 */
public class TimeWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> dataStream = env.socketTextStream("10.187.1.134", 8888);
        SingleOutputStreamOperator<Tuple2<String, Integer>> result = dataStream.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out) throws Exception {
                String[] fields = line.split(",");
                for (String word : fields) {
                    out.collect(new Tuple2<>(word, 1));
                }
            }
        }).keyBy(0)
                .timeWindow(Time.seconds(10), Time.seconds(5))
                .process(new SumProcessWindowFunction());

        result.print().setParallelism(1);

        env.execute("TimeWindowWordCount");

    }

    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Integer>,Tuple2<String,Integer>,Tuple,TimeWindow> {
        FastDateFormat dataFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Integer>> elements,
                            Collector<Tuple2<String, Integer>> out) {

            System.out.println("当天系统的时间:"+dataFormat.format(System.currentTimeMillis()));

            System.out.println("Window的处理时间:"+dataFormat.format(context.currentProcessingTime()));
            System.out.println("Window的开始时间:"+dataFormat.format(context.window().getStart()));
            System.out.println("Window的结束时间:"+dataFormat.format(context.window().getEnd()));

            int sum = 0;
            for (Tuple2<String, Integer> ele : elements) {
                sum += 1;
            }
            // 输出单词出现的次数
            out.collect(Tuple2.of(tuple.getField(0), sum));

        }
    }
}

先输入:

hive

然后输入hive,hbase

输出结果:

当天系统的时间:15:10:30
Window的处理时间:15:10:30
Window的开始时间:15:10:20
Window的结束时间:15:10:30
(hive,1)
当天系统的时间:15:10:35
Window的处理时间:15:10:35
Window的开始时间:15:10:25
Window的结束时间:15:10:35
当天系统的时间:15:10:35
Window的处理时间:15:10:35
Window的开始时间:15:10:25
Window的结束时间:15:10:35
(hbase,1)
(hive,1)

根据每隔5秒执行最近10秒的数据,Flink划分的窗口

[00:00:00, 00:00:05) [00:00:05, 00:00:10)
[00:00:10, 00:00:15) [00:00:15, 00:00:20) 
[00:00:20, 00:00:25) [00:00:25, 00:00:30)
[00:00:30, 00:00:35) [00:00:35, 00:00:40)
[00:00:40, 00:00:45) [00:00:45, 00:00:50) 
[00:00:50, 00:00:55) [00:00:55, 00:01:00)
[00:01:00, 00:01:05)  ...

1.3 Time的种类

针对stream数据中的时间,可以分为以下三种:
Event Time:事件产生的时间,它通常由事件中的时间戳描述。
Ingestion time:事件进入Flink的时间
Processing Time:事件被处理时当前系统的时间

在这里插入图片描述

案例演示:
原始日志如下

2018-10-10 10:00:01,134 INFO executor.Executor: Finished task in state 0.0

这条数据进入Flink的时间是2018-10-10 20:00:00,102
到达window处理的时间为2018-10-10 20:00:01,100

2018-10-10 10:00:01,134 是Event time
2018-10-10 20:00:00,102 是Ingestion time
2018-10-10 20:00:01,100 是Processing tim

思考:

如果我们想要统计每分钟内接口调用失败的错误日志个数,使用哪个时间才有意义?

2 Process Time Window(有序)

需求:每隔5秒计算最近10秒的单词出现的次数

自定义source,模拟:第 13 秒的时候连续发送 2 个事件,第 16 秒的时候再发送 1 个事件
在这里插入图片描述

/**
 * 每隔5秒计算最近10秒单词出现的次数
 */
public class TimeWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        DataStreamSource<String> dataStream = env.addSource(new TestSouce());
        SingleOutputStreamOperator<Tuple2<String, Integer>> result = dataStream.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out) throws Exception {
                String[] fields = line.split(",");
                for (String word : fields) {
                    out.collect(new Tuple2<>(word, 1));
                }
            }
        }).keyBy(0)
                .timeWindow(Time.seconds(10), Time.seconds(5))
                .process(new SumProcessWindowFunction());

        result.print().setParallelism(1);

        env.execute("TimeWindowWordCount");

    }


    public static class TestSouce implements SourceFunction<String>{
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        @Override
        public void run(SourceContext<String> ctx) throws Exception {
             // 控制大约在 10 秒的倍数的时间点发送事件
            String currTime = String.valueOf(System.currentTimeMillis());
            while (Integer.valueOf(currTime.substring(currTime.length() - 4)) > 100) {
                currTime = String.valueOf(System.currentTimeMillis());
                continue;
            }
            System.out.println("开始发送事件的时间:" + dateFormat.format(System.currentTimeMillis()));
            // 第 13 秒发送两个事件
            TimeUnit.SECONDS.sleep(13);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 产生了一个事件,但是由于网络原因,事件没有发送
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 第 16 秒发送一个事件
            TimeUnit.SECONDS.sleep(3);
            ctx.collect("hadoop," + System.currentTimeMillis());
            TimeUnit.SECONDS.sleep(300);

        }

        @Override
        public void cancel() {

        }
    }

    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Integer>,Tuple2<String,Integer>,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Integer>> elements,
                            Collector<Tuple2<String, Integer>> out) {

//            System.out.println("当天系统的时间:"+dateFormat.format(System.currentTimeMillis()));
//
//            System.out.println("Window的处理时间:"+dateFormat.format(context.currentProcessingTime()));
//            System.out.println("Window的开始时间:"+dateFormat.format(context.window().getStart()));
//            System.out.println("Window的结束时间:"+dateFormat.format(context.window().getEnd()));

            int sum = 0;
            for (Tuple2<String, Integer> ele : elements) {
                sum += 1;
            }
            // 输出单词出现的次数
            out.collect(Tuple2.of(tuple.getField(0), sum));

        }
    }
}

输出结果:

开始发送事件的时间:16:16:40
(hadoop,2)
(1573287413001,1)
(1573287413015,1)
(hadoop,3)
(1573287416016,1)
(1573287413001,1)
(1573287413015,1)
(hadoop,1)
(1573287416016,1)

在这里插入图片描述

3 Process Time Window(无序)

自定义source,模拟:第 13 秒的时候连续发送 2 个事件,但是有一个事件确实在第13秒的发送出去了,另外一个事件因为某种原因在19秒的时候才发送出去,第 16 秒的时候再发送 1 个事件

/**
 * 每隔5秒计算最近10秒单词出现的次数
 */
public class TimeWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        DataStreamSource<String> dataStream = env.addSource(new TestSouce());
        SingleOutputStreamOperator<Tuple2<String, Integer>> result = dataStream.flatMap(new FlatMapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public void flatMap(String line, Collector<Tuple2<String, Integer>> out) throws Exception {
                String[] fields = line.split(",");
                for (String word : fields) {
                    out.collect(new Tuple2<>(word, 1));
                }
            }
        }).keyBy(0)
                .timeWindow(Time.seconds(10), Time.seconds(5))
                .process(new SumProcessWindowFunction());

        result.print().setParallelism(1);

        env.execute("TimeWindowWordCount");

    }

    /**
     * 模拟:第 13 秒的时候连续发送 2 个事件,第 16 秒的时候再发送 1 个事件
     */
    public static class TestSouce implements SourceFunction<String>{
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        @Override
        public void run(SourceContext<String> ctx) throws Exception {
            // 控制大约在 10 秒的倍数的时间点发送事件
            String currTime = String.valueOf(System.currentTimeMillis());
            while (Integer.valueOf(currTime.substring(currTime.length() - 4)) > 100) {
                currTime = String.valueOf(System.currentTimeMillis());
                continue;
            }
            System.out.println("开始发送事件的时间:" + dateFormat.format(System.currentTimeMillis()));
            // 第 13 秒发送两个事件
            TimeUnit.SECONDS.sleep(13);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 产生了一个事件,但是由于网络原因,事件没有发送
            String event = "hadoop," + System.currentTimeMillis();
            // 第 16 秒发送一个事件
            TimeUnit.SECONDS.sleep(3);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 第 19 秒的时候发送
            TimeUnit.SECONDS.sleep(3);
            ctx.collect(event);

            TimeUnit.SECONDS.sleep(300);

        }

        @Override
        public void cancel() {

        }
    }

    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Integer>,Tuple2<String,Integer>,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Integer>> elements,
                            Collector<Tuple2<String, Integer>> out) {

//            System.out.println("当天系统的时间:"+dateFormat.format(System.currentTimeMillis()));
//
//            System.out.println("Window的处理时间:"+dateFormat.format(context.currentProcessingTime()));
//            System.out.println("Window的开始时间:"+dateFormat.format(context.window().getStart()));
//            System.out.println("Window的结束时间:"+dateFormat.format(context.window().getEnd()));

            int sum = 0;
            for (Tuple2<String, Integer> ele : elements) {
                sum += 1;
            }
            // 输出单词出现的次数
            out.collect(Tuple2.of(tuple.getField(0), sum));

        }
    }
}

处理结果:

开始发送事件的时间:16:18:50
(hadoop,1)
(1573287543001,1)
(1573287543001,1)
(hadoop,3)
(1573287546016,1)
(1573287543016,1)
(1573287546016,1)
(hadoop,2)
(1573287543016,1)

在这里插入图片描述

4 使用Event Time处理无序

使用Event Time处理

/**
 * 每隔5秒计算最近10秒单词出现的次数
 */
public class TimeWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //步骤一:设置时间类型
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        DataStreamSource<String> dataStream = env.addSource(new TestSouce());
       dataStream.map(new MapFunction<String, Tuple2<String,Long>>() {
            @Override
            public Tuple2<String, Long> map(String line) throws Exception {
                String[] fields = line.split(",");
                return new Tuple2<>(fields[0],Long.valueOf(fields[1]));
            }
            //步骤二:获取数据里面的event Time
        }).assignTimestampsAndWatermarks(new EventTimeExtractor() )
               .keyBy(0)
                .timeWindow(Time.seconds(10), Time.seconds(5))
                .process(new SumProcessWindowFunction())
                .print().setParallelism(1);

        env.execute("TimeWindowWordCount");

    }


    public static class TestSouce implements SourceFunction<String>{
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        @Override
        public void run(SourceContext<String> ctx) throws Exception {
            // 控制大约在 10 秒的倍数的时间点发送事件
            String currTime = String.valueOf(System.currentTimeMillis());
            while (Integer.valueOf(currTime.substring(currTime.length() - 4)) > 100) {
                currTime = String.valueOf(System.currentTimeMillis());
                continue;
            }
            System.out.println("开始发送事件的时间:" + dateFormat.format(System.currentTimeMillis()));
            // 第 13 秒发送两个事件
            TimeUnit.SECONDS.sleep(13);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 产生了一个事件,但是由于网络原因,事件没有发送
            String event = "hadoop," + System.currentTimeMillis();
            // 第 16 秒发送一个事件
            TimeUnit.SECONDS.sleep(3);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 第 19 秒的时候发送
            TimeUnit.SECONDS.sleep(3);
            ctx.collect(event);

            TimeUnit.SECONDS.sleep(300);

        }

        @Override
        public void cancel() {

        }
    }

    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Long>,Tuple2<String,Integer>,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Long>> elements,
                            Collector<Tuple2<String, Integer>> out) {

//            System.out.println("当天系统的时间:"+dateFormat.format(System.currentTimeMillis()));
//
//            System.out.println("Window的处理时间:"+dateFormat.format(context.currentProcessingTime()));
//            System.out.println("Window的开始时间:"+dateFormat.format(context.window().getStart()));
//            System.out.println("Window的结束时间:"+dateFormat.format(context.window().getEnd()));

            int sum = 0;
            for (Tuple2<String, Long> ele : elements) {
                sum += 1;
            }
            // 输出单词出现的次数
            out.collect(Tuple2.of(tuple.getField(0), sum));

        }
    }


    private static class EventTimeExtractor
            implements AssignerWithPeriodicWatermarks<Tuple2<String, Long>> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");

        // 拿到每一个事件的 Event Time
        @Override
        public long extractTimestamp(Tuple2<String, Long> element,
                                     long previousElementTimestamp) {
            return element.f1;
        }

        @Nullable
        @Override
        public Watermark getCurrentWatermark() {

            return new Watermark(System.currentTimeMillis());
        }
    }
}

计算结果:

开始发送事件的时间:16:44:10
(hadoop,1)
(hadoop,3)
(hadoop,1)

在这里插入图片描述

现在我们第三个window的结果已经计算准确了,但是我们还是没有彻底的解决问题。接下来就需要我们使用WaterMark机制来解决了。

5 使用WaterMark机制解决无序

在这里插入图片描述

/**
 * 每隔5秒计算最近10秒单词出现的次数
 */
public class TimeWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //步骤一:设置时间类型
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        DataStreamSource<String> dataStream = env.addSource(new TestSouce());
       dataStream.map(new MapFunction<String, Tuple2<String,Long>>() {
            @Override
            public Tuple2<String, Long> map(String line) throws Exception {
                String[] fields = line.split(",");
                return new Tuple2<>(fields[0],Long.valueOf(fields[1]));
            }
            //步骤二:获取数据里面的event Time
        }).assignTimestampsAndWatermarks(new EventTimeExtractor() )
               .keyBy(0)
                .timeWindow(Time.seconds(10), Time.seconds(5))
                .process(new SumProcessWindowFunction())
                .print().setParallelism(1);

        env.execute("TimeWindowWordCount");

    }


    public static class TestSouce implements SourceFunction<String>{
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        @Override
        public void run(SourceContext<String> ctx) throws Exception {
            // 控制大约在 10 秒的倍数的时间点发送事件
            String currTime = String.valueOf(System.currentTimeMillis());
            while (Integer.valueOf(currTime.substring(currTime.length() - 4)) > 100) {
                currTime = String.valueOf(System.currentTimeMillis());
                continue;
            }
            System.out.println("开始发送事件的时间:" + dateFormat.format(System.currentTimeMillis()));
            // 第 13 秒发送两个事件
            TimeUnit.SECONDS.sleep(13);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 产生了一个事件,但是由于网络原因,事件没有发送
            String event = "hadoop," + System.currentTimeMillis();
            // 第 16 秒发送一个事件
            TimeUnit.SECONDS.sleep(3);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 第 19 秒的时候发送
            TimeUnit.SECONDS.sleep(3);
            ctx.collect(event);

            TimeUnit.SECONDS.sleep(300);

        }

        @Override
        public void cancel() {

        }
    }

    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Long>,Tuple2<String,Integer>,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Long>> elements,
                            Collector<Tuple2<String, Integer>> out) {

//            System.out.println("当天系统的时间:"+dateFormat.format(System.currentTimeMillis()));
//
//            System.out.println("Window的处理时间:"+dateFormat.format(context.currentProcessingTime()));
//            System.out.println("Window的开始时间:"+dateFormat.format(context.window().getStart()));
//            System.out.println("Window的结束时间:"+dateFormat.format(context.window().getEnd()));

            int sum = 0;
            for (Tuple2<String, Long> ele : elements) {
                sum += 1;
            }
            // 输出单词出现的次数
            out.collect(Tuple2.of(tuple.getField(0), sum));

        }
    }


    private static class EventTimeExtractor
            implements AssignerWithPeriodicWatermarks<Tuple2<String, Long>> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");

        // 拿到每一个事件的 Event Time
        @Override
        public long extractTimestamp(Tuple2<String, Long> element,
                                     long previousElementTimestamp) {
            return element.f1;
        }

        @Nullable
        @Override
        public Watermark getCurrentWatermark() {
            //window延迟5秒触发
            return new Watermark(System.currentTimeMillis() - 5000);
        }
    }
}

计算结果:

开始发送事件的时间:16:57:40
(hadoop,2)
(hadoop,3)
(hadoop,1)

结果正确!

6 WaterMark机制

6.1 WaterMark的周期

/**
 * 每隔5秒计算最近10秒单词出现的次数
 */
public class TimeWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //步骤一:设置时间类型
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        //设置waterMark产生的周期为1s
        env.getConfig().setAutoWatermarkInterval(1000);

        DataStreamSource<String> dataStream = env.addSource(new TestSouce());
       dataStream.map(new MapFunction<String, Tuple2<String,Long>>() {
            @Override
            public Tuple2<String, Long> map(String line) throws Exception {
                String[] fields = line.split(",");
                return new Tuple2<>(fields[0],Long.valueOf(fields[1]));
            }
            //步骤二:获取数据里面的event Time
        }).assignTimestampsAndWatermarks(new EventTimeExtractor() )
               .keyBy(0)
                .timeWindow(Time.seconds(10), Time.seconds(5))
                .process(new SumProcessWindowFunction())
                .print().setParallelism(1);

        env.execute("TimeWindowWordCount");

    }


    public static class TestSouce implements SourceFunction<String>{
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        @Override
        public void run(SourceContext<String> ctx) throws Exception {
            // 控制大约在 10 秒的倍数的时间点发送事件
            String currTime = String.valueOf(System.currentTimeMillis());
            while (Integer.valueOf(currTime.substring(currTime.length() - 4)) > 100) {
                currTime = String.valueOf(System.currentTimeMillis());
                continue;
            }
            System.out.println("开始发送事件的时间:" + dateFormat.format(System.currentTimeMillis()));
            // 第 13 秒发送两个事件
            TimeUnit.SECONDS.sleep(13);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 产生了一个事件,但是由于网络原因,事件没有发送
            String event = "hadoop," + System.currentTimeMillis();
            // 第 16 秒发送一个事件
            TimeUnit.SECONDS.sleep(3);
            ctx.collect("hadoop," + System.currentTimeMillis());
            // 第 19 秒的时候发送
            TimeUnit.SECONDS.sleep(3);
            ctx.collect(event);

            TimeUnit.SECONDS.sleep(300);

        }

        @Override
        public void cancel() {

        }
    }

    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Long>,Tuple2<String,Integer>,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Long>> elements,
                            Collector<Tuple2<String, Integer>> out) {
            int sum = 0;
            for (Tuple2<String, Long> ele : elements) {
                sum += 1;
            }
            // 输出单词出现的次数
            out.collect(Tuple2.of(tuple.getField(0), sum));

        }
    }


    private static class EventTimeExtractor
            implements AssignerWithPeriodicWatermarks<Tuple2<String, Long>> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");

        // 拿到每一个事件的 Event Time
        @Override
        public long extractTimestamp(Tuple2<String, Long> element,
                                     long previousElementTimestamp) {
            //这个方法是每获取到一个数据就会被调用一次。
            return element.f1;
        }

        @Nullable
        @Override
        public Watermark getCurrentWatermark() {
            /**
             * WasterMark会周期性的产生,默认就是每隔200毫秒产生一个
             *
             *         设置 watermark 产生的周期为 1000ms
             *         env.getConfig().setAutoWatermarkInterval(1000);
             */
            //window延迟5秒触发
            System.out.println("water mark...");
            return new Watermark(System.currentTimeMillis() - 5000);
        }
    }
}

输出结果:

water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
开始发送事件的时间:17:10:50
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
water mark...
(hadoop,2)
water mark...
water mark...
water mark...
water mark...
water mark...
(hadoop,3)
water mark...
water mark...
water mark...
water mark...
water mark...
(hadoop,1)
water mark...
water mark...
water mark...
water mark...
water mark...

6.2 WaterMark的定义

使用eventTime的时候如何处理乱序数据?
我们知道,流处理从事件产生,到流经source,再到operator,中间是有一个过程和时间的。虽然大部分情况下,流到operator的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络延迟等原因,导致乱序的产生,特别是使用kafka的话,多个分区的数据无法保证有序。所以在进行window计算的时候,我们又不能无限期的等下去,必须要有个机制来保证一个特定的时间后,必须触发window去进行计算了。这个特别的机制,就是watermark,watermark是用于处理乱序事件的。watermark可以翻译为水位线

有序的流的watermarks

在这里插入图片描述

无序的流的watermarks

在这里插入图片描述

多并行度流的watermarks

在这里插入图片描述

6.3 需求

得到并打印每隔 3 秒钟统计前 3 秒内的相同的 key 的所有的事件

在这里插入图片描述

代码开发

/**
 * 得到并打印每隔 3 秒钟统计前 3 秒内的相同的 key 的所有的事件
 */
public class WaterMarkWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //步骤一:设置时间类型
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        //设置waterMark产生的周期为1s
        env.getConfig().setAutoWatermarkInterval(1000);

        DataStreamSource<String> dataStream = env.socketTextStream("10.187.1.134", 8888);
        dataStream.map(new MapFunction<String, Tuple2<String,Long>>() {
            @Override
            public Tuple2<String, Long> map(String line) throws Exception {
                String[] fields = line.split(",");
                return new Tuple2<>(fields[0],Long.valueOf(fields[1]));
            }
            //步骤二:获取数据里面的event Time
        }).assignTimestampsAndWatermarks(new EventTimeExtractor() )
               .keyBy(0)
                .timeWindow(Time.seconds(3))
                .process(new SumProcessWindowFunction())
                .print().setParallelism(1);

        env.execute("TimeWindowWordCount");

    }




    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Long>,String,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Long>> elements,
                            Collector<String> out) {
            System.out.println("处理时间:" + dateFormat.format(context.currentProcessingTime()));
            System.out.println("window start time : " + dateFormat.format(context.window().getStart()));

            List<String> list = new ArrayList<>();
            for (Tuple2<String, Long> ele : elements) {
                list.add(ele.toString() + "|" + dateFormat.format(ele.f1));
            }
            out.collect(list.toString());
            System.out.println("window end time  : " + dateFormat.format(context.window().getEnd()));

        }
    }


    private static class EventTimeExtractor
            implements AssignerWithPeriodicWatermarks<Tuple2<String, Long>> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");

        private long currentMaxEventTime = 0L;
        private long maxOutOfOrderness = 10000; // 最大允许的乱序时间 10 秒


        // 拿到每一个事件的 Event Time
        @Override
        public long extractTimestamp(Tuple2<String, Long> element,
                                     long previousElementTimestamp) {
            long currentElementEventTime = element.f1;
            currentMaxEventTime = Math.max(currentMaxEventTime, currentElementEventTime);
            System.out.println("event = " + element
                    + "|" + dateFormat.format(element.f1) // Event Time
                    + "|" + dateFormat.format(currentMaxEventTime)  // Max Event Time
                    + "|" + dateFormat.format(getCurrentWatermark().getTimestamp())); // Current Watermark
            return currentElementEventTime;
        }

        @Nullable
        @Override
        public Watermark getCurrentWatermark() {
            /**
             * WasterMark会周期性的产生,默认就是每隔200毫秒产生一个
             *
             *         设置 watermark 产生的周期为 1000ms
             *         env.getConfig().setAutoWatermarkInterval(1000);
             */
            //window延迟5秒触发
            System.out.println("water mark...");
            return new Watermark(currentMaxEventTime - maxOutOfOrderness);
        }
    }
}

演示数据:

-- window 计算触发的条件
000001,1461756862000
000001,1461756866000
000001,1461756872000
000001,1461756873000
000001,1461756874000
000001,1461756876000
000001,1461756877000

一条一条的数据输入。

6.4计算window的触发时间

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

总结:window触发的时间

  1. watermark 时间 >= window_end_time
  2. 在 [window_start_time, window_end_time) 区间中有数据存在,注意是左闭右开的区间,而且是以 event time 来计算的

4.6.5 WaterMark+Window 处理乱序时间

输入数据:

000001,1461756879000
000001,1461756871000

000001,1461756883000

在这里插入图片描述

在这里插入图片描述

6.5 迟到太多的事件

在这里插入图片描述

  1. 丢弃,这个是默认的处理方式
  2. allowedLateness 指定允许数据延迟的时间
  3. sideOutputLateData 收集迟到的数据

丢弃

重启程序,做测试。

输入数据:

000001,1461756870000
000001,1461756883000

000001,1461756870000
000001,1461756871000
000001,1461756872000

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

发现迟到太多数据就会被丢弃

指定允许再次迟到的时间

).assignTimestampsAndWatermarks(new EventTimeExtractor() )
               .keyBy(0)
                .timeWindow(Time.seconds(3))
                .allowedLateness(Time.seconds(2)) // 允许事件迟到 2 秒
                .process(new SumProcessWindowFunction())
                .print().setParallelism(1);

输入数据

000001,1461756870000
000001,1461756883000

000001,1461756870000
000001,1461756871000
000001,1461756872000

000001,1461756884000

000001,1461756870000
000001,1461756871000
000001,1461756872000

000001,1461756885000

000001,1461756870000
000001,1461756871000
000001,1461756872000

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1573297613203

  1. 当我们设置允许迟到 2 秒的事件,第一次 window 触发的条件是 watermark >= window_end_time
  2. 第二次(或者多次)触发的条件是 watermark < window_end_time + allowedLateness

收集迟到的数据

/**
 * 得到并打印每隔 3 秒钟统计前 3 秒内的相同的 key 的所有的事件
 * 收集迟到太多的数据
 */
public class WaterMarkWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        //步骤一:设置时间类型
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        //设置waterMark产生的周期为1s
        env.getConfig().setAutoWatermarkInterval(1000);

        // 保存迟到的,会被丢弃的数据
        OutputTag<Tuple2<String, Long>> outputTag =
                new OutputTag<Tuple2<String, Long>>("late-date"){};

        DataStreamSource<String> dataStream = env.socketTextStream("10.187.1.134", 8888);
        SingleOutputStreamOperator<String> result = dataStream.map(new MapFunction<String, Tuple2<String, Long>>() {
            @Override
            public Tuple2<String, Long> map(String line) throws Exception {
                String[] fields = line.split(",");
                return new Tuple2<>(fields[0], Long.valueOf(fields[1]));
            }
            //步骤二:获取数据里面的event Time
        }).assignTimestampsAndWatermarks(new EventTimeExtractor())
                .keyBy(0)
                .timeWindow(Time.seconds(3))
                // .allowedLateness(Time.seconds(2)) // 允许事件迟到 2 秒
                .sideOutputLateData(outputTag) // 保存迟到太多的数据
                .process(new SumProcessWindowFunction());
        //打印正常的数据
        result.print();
        //获取迟到太多的数据

        DataStream<String> lateDataStream
                = result.getSideOutput(outputTag).map(new MapFunction<Tuple2<String, Long>, String>() {
            @Override
            public String map(Tuple2<String, Long> stringLongTuple2) throws Exception {
                return "迟到的数据:" + stringLongTuple2.toString();
            }
        });

        lateDataStream.print();

        env.execute("TimeWindowWordCount");

    }




    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Long>,String,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Long>> elements,
                            Collector<String> out) {
            System.out.println("处理时间:" + dateFormat.format(context.currentProcessingTime()));
            System.out.println("window start time : " + dateFormat.format(context.window().getStart()));

            List<String> list = new ArrayList<>();
            for (Tuple2<String, Long> ele : elements) {
                list.add(ele.toString() + "|" + dateFormat.format(ele.f1));
            }
            out.collect(list.toString());
            System.out.println("window end time  : " + dateFormat.format(context.window().getEnd()));

        }
    }


    private static class EventTimeExtractor
            implements AssignerWithPeriodicWatermarks<Tuple2<String, Long>> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");

        private long currentMaxEventTime = 0L;
        private long maxOutOfOrderness = 10000; // 最大允许的乱序时间 10 秒


        // 拿到每一个事件的 Event Time
        @Override
        public long extractTimestamp(Tuple2<String, Long> element,
                                     long previousElementTimestamp) {
            long currentElementEventTime = element.f1;
            currentMaxEventTime = Math.max(currentMaxEventTime, currentElementEventTime);
            System.out.println("event = " + element
                    + "|" + dateFormat.format(element.f1) // Event Time
                    + "|" + dateFormat.format(currentMaxEventTime)  // Max Event Time
                    + "|" + dateFormat.format(getCurrentWatermark().getTimestamp())); // Current Watermark
            return currentElementEventTime;
        }

        @Nullable
        @Override
        public Watermark getCurrentWatermark() {
            /**
             * WasterMark会周期性的产生,默认就是每隔200毫秒产生一个
             *
             *         设置 watermark 产生的周期为 1000ms
             *         env.getConfig().setAutoWatermarkInterval(1000);
             */
            System.out.println("water mark...");
            return new Watermark(currentMaxEventTime - maxOutOfOrderness);
        }
    }
}

输入:

000001,1461756870000
000001,1461756883000
迟到的数据
000001,1461756870000
000001,1461756871000
000001,1461756872000

7 多并行度下的WaterMark

在这里插入图片描述

一个window可能会接受到多个waterMark,我们以最小的为准。

/**
 * 得到并打印每隔 3 秒钟统计前 3 秒内的相同的 key 的所有的事件
 * 测试多并行度
 */
public class WaterMarkWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //把并行度设置为2
        env.setParallelism(2);
        //步骤一:设置时间类型
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        //设置waterMark产生的周期为1s
        env.getConfig().setAutoWatermarkInterval(1000);

        // 保存迟到的,会被丢弃的数据
        OutputTag<Tuple2<String, Long>> outputTag =
                new OutputTag<Tuple2<String, Long>>("late-date"){};

        DataStreamSource<String> dataStream = env.socketTextStream("10.187.1.134", 8888);
        SingleOutputStreamOperator<String> result = dataStream.map(new MapFunction<String, Tuple2<String, Long>>() {
            @Override
            public Tuple2<String, Long> map(String line) throws Exception {
                String[] fields = line.split(",");
                return new Tuple2<>(fields[0], Long.valueOf(fields[1]));
            }
            //步骤二:获取数据里面的event Time
        }).assignTimestampsAndWatermarks(new EventTimeExtractor())
                .keyBy(0)
                .timeWindow(Time.seconds(3))
                // .allowedLateness(Time.seconds(2)) // 允许事件迟到 2 秒
                .sideOutputLateData(outputTag) // 保存迟到太多的数据
                .process(new SumProcessWindowFunction());
        //打印正常的数据
        result.print();
        //获取迟到太多的数据

        DataStream<String> lateDataStream
                = result.getSideOutput(outputTag).map(new MapFunction<Tuple2<String, Long>, String>() {
            @Override
            public String map(Tuple2<String, Long> stringLongTuple2) throws Exception {
                return "迟到的数据:" + stringLongTuple2.toString();
            }
        });

        lateDataStream.print();

        env.execute("TimeWindowWordCount");

    }




    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Long>,String,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Long>> elements,
                            Collector<String> out) {
            System.out.println("处理时间:" + dateFormat.format(context.currentProcessingTime()));
            System.out.println("window start time : " + dateFormat.format(context.window().getStart()));

            List<String> list = new ArrayList<>();
            for (Tuple2<String, Long> ele : elements) {
                list.add(ele.toString() + "|" + dateFormat.format(ele.f1));
            }
            out.collect(list.toString());
            System.out.println("window end time  : " + dateFormat.format(context.window().getEnd()));

        }
    }


    private static class EventTimeExtractor
            implements AssignerWithPeriodicWatermarks<Tuple2<String, Long>> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");

        private long currentMaxEventTime = 0L;
        private long maxOutOfOrderness = 10000; // 最大允许的乱序时间 10 秒


        // 拿到每一个事件的 Event Time
        @Override
        public long extractTimestamp(Tuple2<String, Long> element,
                                     long previousElementTimestamp) {
            long currentElementEventTime = element.f1;
            currentMaxEventTime = Math.max(currentMaxEventTime, currentElementEventTime);
            //打印线程
            long id = Thread.currentThread().getId();
            System.out.println("当前线程ID:"+id+"event = " + element
                    + "|" + dateFormat.format(element.f1) // Event Time
                    + "|" + dateFormat.format(currentMaxEventTime)  // Max Event Time
                    + "|" + dateFormat.format(getCurrentWatermark().getTimestamp())); // Current Watermark
            return currentElementEventTime;
        }

        @Nullable
        @Override
        public Watermark getCurrentWatermark() {
            /**
             * WasterMark会周期性的产生,默认就是每隔200毫秒产生一个
             *
             *         设置 watermark 产生的周期为 1000ms
             *         env.getConfig().setAutoWatermarkInterval(1000);
             */
            System.out.println("water mark...");
            return new Watermark(currentMaxEventTime - maxOutOfOrderness);
        }
    }
}

输入数据:

000001,1461756870000
000001,1461756883000
000001,1461756888000

输出结果:

当前线程ID:55event = (000001,1461756883000)|19:34:43|19:34:43|19:34:33
water mark...
当前线程ID:56event = (000001,1461756870000)|19:34:30|19:34:30|19:34:20
water mark...
water mark...
water mark...
当前线程ID:56event = (000001,1461756888000)|19:34:48|19:34:48|19:34:38
water mark...
water mark...
处理时间:19:31:25
window start time : 19:34:30
2> [(000001,1461756870000)|19:34:30]
window end time  : 19:34:33

ID为56的线程有两个WaterMark:20,38

那么38这个会替代20,所以ID为56的线程的WaterMark是38

然后ID为55的线程的WaterMark是33,而ID为56是WaterMark是38,会在里面求一个小的值作为waterMark,就是33,这个时候会触发Window为30-33的窗口,那这个窗口里面就有 (000001,1461756870000)这条数据。

8 WaterMark生成机制

/**
 * 得到并打印每隔 3 秒钟统计前 3 秒内的相同的 key 的所有的事件
 * 有条件的产生watermark
 */
public class WaterMarkWindowWordCount {
    public static void main(String[] args) throws Exception{
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        //把并行度设置为2
        env.setParallelism(2);
        //步骤一:设置时间类型
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        //设置waterMark产生的周期为1s
        env.getConfig().setAutoWatermarkInterval(1000);

        // 保存迟到的,会被丢弃的数据
        OutputTag<Tuple2<String, Long>> outputTag =
                new OutputTag<Tuple2<String, Long>>("late-date"){};

        DataStreamSource<String> dataStream = env.socketTextStream("10.187.1.134", 8888);
        SingleOutputStreamOperator<String> result = dataStream.map(new MapFunction<String, Tuple2<String, Long>>() {
            @Override
            public Tuple2<String, Long> map(String line) throws Exception {
                String[] fields = line.split(",");
                return new Tuple2<>(fields[0], Long.valueOf(fields[1]));
            }
            //步骤二:获取数据里面的event Time
        }).assignTimestampsAndWatermarks(new EventTimeExtractor())
                .keyBy(0)
                .timeWindow(Time.seconds(3))
                // .allowedLateness(Time.seconds(2)) // 允许事件迟到 2 秒
                .sideOutputLateData(outputTag) // 保存迟到太多的数据
                .process(new SumProcessWindowFunction());
        //打印正常的数据
        result.print();
        //获取迟到太多的数据

        DataStream<String> lateDataStream
                = result.getSideOutput(outputTag).map(new MapFunction<Tuple2<String, Long>, String>() {
            @Override
            public String map(Tuple2<String, Long> stringLongTuple2) throws Exception {
                return "迟到的数据:" + stringLongTuple2.toString();
            }
        });

        lateDataStream.print();

        env.execute("TimeWindowWordCount");

    }




    /**
     * IN, OUT, KEY, W
     * IN:输入的数据类型
     * OUT:输出的数据类型
     * Key:key的数据类型(在Flink里面,String用Tuple表示)
     * W:Window的数据类型
     */
    public static class SumProcessWindowFunction extends
            ProcessWindowFunction<Tuple2<String,Long>,String,Tuple,TimeWindow> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");
        /**
         * 当一个window触发计算的时候会调用这个方法
         * @param tuple key
         * @param context operator的上下文
         * @param elements 指定window的所有元素
         * @param out 用户输出
         */
        @Override
        public void process(Tuple tuple, Context context, Iterable<Tuple2<String, Long>> elements,
                            Collector<String> out) {
            System.out.println("处理时间:" + dateFormat.format(context.currentProcessingTime()));
            System.out.println("window start time : " + dateFormat.format(context.window().getStart()));

            List<String> list = new ArrayList<>();
            for (Tuple2<String, Long> ele : elements) {
                list.add(ele.toString() + "|" + dateFormat.format(ele.f1));
            }
            out.collect(list.toString());
            System.out.println("window end time  : " + dateFormat.format(context.window().getEnd()));

        }
    }

    /**
     * 按条件产生waterMark
     */
    private static class EventTimeExtractor2
            implements AssignerWithPunctuatedWatermarks<Tuple2<String, Long>> {

        @Nullable
        @Override
        public Watermark checkAndGetNextWatermark(Tuple2<String, Long> lastElement,
                                                  long extractedTimestamp) {
            // 这个方法是每接收到一个事件就会调用
            // 根据条件产生 watermark ,并不是周期性的产生 watermark
            if (lastElement.f0 == "000002") {
                // 才发送 watermark
                return new Watermark(lastElement.f1 - 10000);
            }
            // 则表示不产生 watermark
            return null;
        }

        @Override
        public long extractTimestamp(Tuple2<String, Long> element,
                                     long previousElementTimestamp) {
            return element.f1;
        }
    }


    private static class EventTimeExtractor
            implements AssignerWithPeriodicWatermarks<Tuple2<String, Long>> {
        FastDateFormat dateFormat = FastDateFormat.getInstance("HH:mm:ss");

        private long currentMaxEventTime = 0L;
        private long maxOutOfOrderness = 10000; // 最大允许的乱序时间 10 秒


        // 拿到每一个事件的 Event Time
        @Override
        public long extractTimestamp(Tuple2<String, Long> element,
                                     long previousElementTimestamp) {
            long currentElementEventTime = element.f1;
            currentMaxEventTime = Math.max(currentMaxEventTime, currentElementEventTime);
            long id = Thread.currentThread().getId();
            System.out.println("当前线程ID:"+id+"event = " + element
                    + "|" + dateFormat.format(element.f1) // Event Time
                    + "|" + dateFormat.format(currentMaxEventTime)  // Max Event Time
                    + "|" + dateFormat.format(getCurrentWatermark().getTimestamp())); // Current Watermark
            return currentElementEventTime;
        }

        @Nullable
        @Override
        public Watermark getCurrentWatermark() {
            /**
             * WasterMark会周期性的产生,默认就是每隔200毫秒产生一个
             *
             *         设置 watermark 产生的周期为 1000ms
             *         env.getConfig().setAutoWatermarkInterval(1000);
             *
             *
             * 和事件关系不大
             *    1. watermark 值依赖处理时间的场景
             *    2. 当有一段时间没有接收到事件,但是仍然需要产生 watermark 的场景
             */
            System.out.println("water mark...");
            return new Watermark(currentMaxEventTime - maxOutOfOrderness);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值