flink----11 Time 和wartermark

针对stream数据中的时间,可以分为以下三种

Event Time:事件产生的时间,它通常由事件中的时间戳描述。

Ingestion time:事件进入Flink的时间

Processing Time:事件被处理时当前系统的时间

 

l原始日志如下

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

l这条数据进入Flink的时间是2018-10-10 20:00:00,102

l到达window处理的时间为2018-10-10 20:00:01,100


lFlink中,默认Time类似是ProcessingTime

l如果我们想要统计每分钟内接口调用失败的错误日志个数,就需要把时间设置成事件产生时间

-------------------------------------

l在使用eventTime的时候如何处理乱序数据?

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

lwatermark可以翻译为水位线

有序的流的watermarks

无序的流的watermarks


多并行度流的watermarks

l注意:多并行度的情况下,watermark对齐会取所有channel最小的watermark

watermarks的生成方式

 

l通常,在接收到source的数据后,应该立刻生成watermark;但是,也可以在source后,应用简单的map或者filter操作后,再生成watermark。

l注意:如果指定多次watermark,后面指定的会覆盖前面的值。

l生成方式

With Periodic Watermarks

周期性的触发watermark的生成和发送,默认是100ms

每隔N秒自动向流里注入一个WATERMARK 时间间隔由ExecutionConfig.setAutoWatermarkInterval 决定. 每次调用getCurrentWatermark 方法, 如果得到的WATERMARK 不为空并且比之前的大就注入流中

可以定义一个最大允许乱序的时间,这种比较常用

实现AssignerWithPeriodicWatermarks接口

With Punctuated Watermarks

基于某些事件触发watermark的生成和发送

基于事件向流里注入一个WATERMARK,每一个元素都有机会判断是否生成一个WATERMARK. 如果得到的WATERMARK 不为空并且比之前的大就注入流中

实现AssignerWithPunctuatedWatermarks接口

 

-----------------------------------------

由于wartermark比较复杂,下面用一个例子来说明

With Periodic Watermarks案例总结

代码

public class StreamingWindowWatermark2 {

    public static void main(String[] args) throws Exception {
        //定义socket的端口号
        int port = 9000;
        //获取运行环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        //设置使用eventtime,默认是使用processtime
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        //设置并行度为1,默认并行度是当前机器的cpu数量
        env.setParallelism(1);

        //连接socket获取输入的数据
        DataStream<String> text = env.socketTextStream("hadoop100", port, "\n");

        //解析输入的数据
        DataStream<Tuple2<String, Long>> inputMap = text.map(new MapFunction<String, Tuple2<String, Long>>() {
            @Override
            public Tuple2<String, Long> map(String value) throws Exception {
                String[] arr = value.split(",");
                return new Tuple2<>(arr[0], Long.parseLong(arr[1]));
            }
        });

        //抽取timestamp和生成watermark
        DataStream<Tuple2<String, Long>> waterMarkStream = inputMap.assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks<Tuple2<String, Long>>() {

            Long currentMaxTimestamp = 0L;
            final Long maxOutOfOrderness = 10000L;// 最大允许的乱序时间是10s

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            /**
             * 定义生成watermark的逻辑
             * 默认100ms被调用一次
             */
            @Nullable
            @Override
            public Watermark getCurrentWatermark() {
                return new Watermark(currentMaxTimestamp - maxOutOfOrderness);
            }

            //定义如何提取timestamp
            @Override
            public long extractTimestamp(Tuple2<String, Long> element, long previousElementTimestamp) {
                long timestamp = element.f1;
                currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp);
                System.out.println("key:"+element.f0+",eventtime:["+element.f1+"|"+sdf.format(element.f1)+"],currentMaxTimestamp:["+currentMaxTimestamp+"|"+
                        sdf.format(currentMaxTimestamp)+"],watermark:["+getCurrentWatermark().getTimestamp()+"|"+sdf.format(getCurrentWatermark().getTimestamp())+"]");
                return timestamp;
            }
        });

        //保存被丢弃的数据
        OutputTag<Tuple2<String, Long>> outputTag = new OutputTag<Tuple2<String, Long>>("late-data"){};
        //注意,由于getSideOutput方法是SingleOutputStreamOperator子类中的特有方法,所以这里的类型,不能使用它的父类dataStream。
        SingleOutputStreamOperator<String> window = waterMarkStream.keyBy(0)
                .window(TumblingEventTimeWindows.of(Time.seconds(3)))//按照消息的EventTime分配窗口,和调用TimeWindow效果一样
                //.allowedLateness(Time.seconds(2))//允许数据迟到2秒
                .sideOutputLateData(outputTag)
                .apply(new WindowFunction<Tuple2<String, Long>, String, Tuple, TimeWindow>() {
                    /**
                     * 对window内的数据进行排序,保证数据的顺序
                     * @param tuple
                     * @param window
                     * @param input
                     * @param out
                     * @throws Exception
                     */
                    @Override
                    public void apply(Tuple tuple, TimeWindow window, Iterable<Tuple2<String, Long>> input, Collector<String> out) throws Exception {
                        String key = tuple.toString();
                        List<Long> arrarList = new ArrayList<Long>();
                        Iterator<Tuple2<String, Long>> it = input.iterator();
                        while (it.hasNext()) {
                            Tuple2<String, Long> next = it.next();
                            arrarList.add(next.f1);
                        }
                        Collections.sort(arrarList);
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                        String result = key + "," + arrarList.size() + "," + sdf.format(arrarList.get(0)) + "," + sdf.format(arrarList.get(arrarList.size() - 1))
                                + "," + sdf.format(window.getStart()) + "," + sdf.format(window.getEnd());
                        out.collect(result);
                    }
                });
        //把迟到的数据暂时打印到控制台,实际中可以保存到其他存储介质中
        DataStream<Tuple2<String, Long>> sideOutput = window.getSideOutput(outputTag);
        sideOutput.print();
        //测试-把结果打印到控制台即可
        window.print();

        //注意:因为flink是懒加载的,所以必须调用execute方法,上面的代码才会执行
        env.execute("eventtime-watermark");

    }



}

-----

程序运行分析:

首先我们开启socket,输入第一条数据:

nc -l 9000

0001,1538359882000

输出内容并没有触发window操作

为了查看方便,我们把输入的内容汇总到表格中

Key

Event Time

CurrentMaxTimeStamp

WaterMark

0001

1538359882000
1538359882000
1538359872000

2018-10-01 10:11:22.000

2018-10-01 10:11:22.000

2018-10-01 10:11:12.000

 

此时,wartermark 的时间,已经落后于 currentMaxTimestamp10 秒了。我们继续输入

nc -l 9000

0001,1538359882000

0001,1538359886000

此时也没有触发window

Key

Event Time

CurrentMaxTimeStamp

WaterMark

0001

1538359882000
1538359882000
1538359872000

2018-10-01 10:11:22.000

2018-10-01 10:11:22.000

2018-10-01 10:11:12.000

0001

1538359886000
1538359886000
1538359876000

2018-10-01 10:11:26.000

2018-10-01 10:11:26.000

2018-10-01 10:11:16.000

 

继续输入:

nc -l 9000

0001,1538359882000

0001,1538359886000

0001,1538359892000

也没有触发window

Key

Event Time

CurrentMaxTimeStamp

WaterMark

0001

1538359882000
1538359882000
1538359872000

2018-10-01 10:11:22.000

2018-10-01 10:11:22.000

2018-10-01 10:11:12.000

0001

1538359886000
1538359886000
1538359876000

2018-10-01 10:11:26.000

2018-10-01 10:11:26.000

2018-10-01 10:11:16.000

0001

1538359892000
1538359892000
1538359882000

2018-10-01 10:11:32.000

2018-10-01 10:11:32.000

2018-10-01 10:11:22.000

到这里,window 仍然没有被触发,此时 watermark 的时间已经等于了第一条数据的 Event Time 了。那么 window 到底什么时候被触发呢?我们再次输入:

nc -l 9000

0001,1538359882000

0001,1538359886000

0001,1538359892000

0001,1538359893000

Key

Event Time

CurrentMaxTimeStamp

WaterMark

0001

1538359882000
1538359882000
1538359872000

2018-10-01 10:11:22.000

2018-10-01 10:11:22.000

2018-10-01 10:11:12.000

0001

1538359886000
1538359886000
1538359876000

2018-10-01 10:11:26.000

2018-10-01 10:11:26.000

2018-10-01 10:11:16.000

0001

1538359892000
1538359892000
1538359882000

2018-10-01 10:11:32.000

2018-10-01 10:11:32.000

2018-10-01 10:11:22.000

0001

1538359893000
1538359893000
1538359883000

2018-10-01 10:11:33.000

2018-10-01 10:11:33.000

2018-10-01 10:11:23.000

window 仍然没有触发,此时,我们的数据已经发到 2018-10-01 10:11:33.000 了,根据 eventtime 来算,最早的数据已经过去了 11 秒了,window 还没有开始计算,那到底什么时 候会触发 window 呢?

我们再次增加 1 秒,输入:

nc -l 9000

0001,1538359882000

0001,1538359886000

0001,1538359892000

0001,1538359893000

0001,1538359894000

这时候触发了window

Key

Event Time

CurrentMaxTim eStamp

WaterMark

window_start _time

window_end_ time

0001

1538359882000
1538359882000
1538359872000
  

2018-10-01 10:11:22.000

2018-10-01 10:11:22.000

2018-10-01 10:11:12.000

  

0001

1538359886000
1538359886000
1538359876000
  

2018-10-01 10:11:26.000

2018-10-01 10:11:26.000

2018-10-01 10:11:16.000

  

0001

1538359892000
1538359892000
1538359882000
  

2018-10-01 10:11:32.000

2018-10-01 10:11:32.000

2018-10-01 10:11:22.000

  

0001

1538359893000
1538359893000
1538359883000
  

2018-10-01 10:11:33.000

2018-10-01 10:11:33.000

2018-10-01 10:11:23.000

  

0001

1538359894000
1538359894000
1538359884000
  

2018-10-01 10:11:34.000

2018-10-01 10:11:34.000

2018-10-01 10:11:24.000

[10:11:21.000

 

到这里,我们做一个说明:
window 的触发机制,是先按照自然时间将 window 划分,如果 window 大小是 3 秒,那么 1 分钟内会把 window 划分为如下的形式【左闭右开】:

[00:00:03,00:00:06) [00:00:06,00:00:09) [00:00:09,00:00:12) [00:00:12,00:00:15) [00:00:15,00:00:18) [00:00:18,00:00:21) [00:00:21,00:00:24) [00:00:24,00:00:27) [00:00:27,00:00:30) [00:00:30,00:00:33) [00:00:33,00:00:36) [00:00:36,00:00:39) [00:00:39,00:00:42) [00:00:42,00:00:45) [00:00:45,00:00:48) [00:00:48,00:00:51) [00:00:51,00:00:54) [00:00:54,00:00:57) [00:00:57,00:01:00) ...

window 的设定无关数据本身,而是系统定义好了的。

输入的数据中,根据自身的 Event Time,将数据划分到不同的 window 中,如果 window 中 有数据,则当 watermark 时间>=Event Time 时,就符合了 window 触发的条件了,最终决定 window 触发,还是由数据本身的 Event Time 所属的 window 中的 window_end_time 决定。

上面的测试中,最后一条数据到达后,其水位线已经升至 10:11:24 秒,正好是最早的一条 记录所在 window 的 window_end_time,所以 window 就被触发了。

为了验证 window 的触发机制,我们继续输入数据:

nc -l 9000

0001,1538359882000

0001,1538359886000

0001,1538359892000

0001,1538359893000

0001,1538359894000

0001,1538359896000

Key

Event Time

CurrentMaxTim eStamp

WaterMark

window_start_ time

window_end_t ime

0001

1538359882000
1538359882000
1538359872000
  

2018-10-01 10:11:22.000

2018-10-01 10:11:22.000

2018-10-01 10:11:12.000

  

0001

1538359886000
1538359886000
1538359876000
  

2018-10-01 10:11:26.000

2018-10-01 10:11:26.000

2018-10-01 10:11:16.000

  

0001

1538359892000
1538359892000
1538359882000
  

2018-10-01 10:11:32.000

2018-10-01 10:11:32.000

2018-10-01 10:11:22.000

  

0001

1538359893000
1538359893000
1538359883000
  

2018-10-01 10:11:33.000

2018-10-01 10:11:33.000

2018-10-01 10:11:23.000

  

0001

1538359894000
1538359894000
1538359884000
  

2018-10-01 10:11:34.000

2018-10-01 10:11:34.000

2018-10-01 10:11:24.000

[10:11:21.000

10:11:24.000)

0001

1538359896000
1538359896000
1538359886000
  

2018-10-01 10:11:36.000

2018-10-01 10:11:36.000

2018-10-01 10:11:26.000

  

此时,watermark 时间虽然已经达到了第二条数据的时间,但是由于其没有达到第二条数据 所在 window 的结束时间,所以 window 并没有被触发。那么,第二条数据所在的 window 时间是:

[00:00:24,00:00:27)

也就是说,我们必须输入一个 10:11:27 秒的数据,第二条数据所在的 window 才会被触发。 我们继续输入:

nc -l 9000

0001,1538359882000

0001,1538359886000

0001,1538359892000

0001,1538359893000

0001,1538359894000

0001,1538359896000

0001,1538359897000

Key

Event Time

CurrentMaxTim eStamp

WaterMark

window_start_ time

window_end_t ime

0001

1538359882000
1538359882000
1538359872000
  

2018-10-01 10:11:22.000

2018-10-01 10:11:22.000

2018-10-01 10:11:12.000

  

0001

1538359886000
1538359886000
1538359876000
  

2018-10-01 10:11:26.000

2018-10-01 10:11:26.000

2018-10-01 10:11:16.000

  

0001

1538359892000
1538359892000
1538359882000
  

2018-10-01 10:11:32.000

2018-10-01 10:11:32.000

2018-10-01 10:11:22.000

  

0001

1538359893000
1538359893000
1538359883000
  

2018-10-01 10:11:33.000

2018-10-01 10:11:33.000

2018-10-01 10:11:23.000

  

0001

1538359894000
1538359894000
1538359884000
  

2018-10-01 10:11:34.000

2018-10-01 10:11:34.000

2018-10-01 10:11:24.000

[10:11:21.000

10:11:24.000)

0001

1538359896000
1538359896000
1538359886000
  

2018-10-01 10:11:36.000

2018-10-01 10:11:36.000

2018-10-01 10:11:26.000

  

0001

1538359897000
1538359897000
1538359887000
  

2018-10-01 10:11:37.000

2018-10-01 10:11:37.000

2018-10-01 10:11:27.000

[10:11:24.000

10:11:27.000)

此时,我们已经看到,window 的触发要符合以下几个条件:

1.wartermark时间 >= window_end_time

2.在[window_start_time,window_end_time)区间中有数据存在,注意是左闭右开

同时满足了以上 2 个条件,window 才会触发。

 

这一节有点长,放点内容在下一节中分析

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值