Flink学习9-WaterMark

WaterMark与EventTime

在EventTime中数据是使用自带的时间戳来进行计算,可以防止 Flink 内部处理数据是发生乱序的情况,但无法解决数据到达 Flink 之前发生的乱序问题。所以,为了解决消息乱序的问题,会让WaterMark结合EventTime使用。这里进行举例说明:

public static void test(StreamExecutionEnvironment env){
        SingleOutputStreamOperator<String> stream = env.socketTextStream("localhost", 9527)
                /**
                 * flink1.12时间类型默认是eventtime,不需要指定
                 * 在使用基于event的窗口函数时,必须在前面指定watermark
                 * 这里设置watermark为0,即表示不接收延迟数据
                 */
                .assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(0)) {
                    @Override
                    public long extractTimestamp(String s) {
                        return Long.parseLong(s.split(",")[0]);
                    }
                });
        stream.map(new MapFunction<String, Tuple2<String,Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String s) throws Exception {
                String[] split = s.split(",");
                return Tuple2.of(split[1],Integer.parseInt(split[2]));
            }
        })
        .keyBy(x -> x.f0)
        .window(TumblingEventTimeWindows.of(Time.seconds(5)))//滚动窗口,每5秒一次
        .sum(1)
        .print();
    }

上述简单做了一个词频计算,这里设置的是每5秒计算一次,不接收延迟数据。输入如下:在这里插入图片描述
这里时间戳输入到5000时触发了窗口计算,会计算[0,5000)这个区间的数据。
在这里插入图片描述
由于网络原因,这个时候来了几条延迟数据:(4500,a,1)、(3200,b,1)、(1000,a,1)因为设置的是零延迟,在数据流时间戳达到10000时触发窗口计算。所以这几条数据就被丢弃,只会计算[5000,10000)这个区间的数据。

WaterMark的使用

WaterMark设定方法有两种:
1、Punctuated Watermark :
数据流中每一个递增的EventTime都会产生一个Watermark。在实际的生产中用Punctuated方式,在TPS很高的场景下,会产生大量的Watermark,在一定程度上造成了对下游算子造成压力。所以只有在实时性要求非常高的场景才会选择Punctuated的方式。

datastream.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<Tuple3<String, Long, Integer>>() {
            @Override
            public long extractAscendingTimestamp(Tuple3<String, Long, Integer> element) {
                return element.f1;
            }
        });

2、Periodic Watermark :
默认200毫秒为一周期(或达到一定的记录条数)产生一个Watermark。在实际的生产中使用Periodic的方式必须结合时间和积累条数两个维度,周期性的产生Watermark,否则在极端情况下会有很大的延时

Time interval = Time.milliseconds(300000L);
ds.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<Tuple3<String, Long, Integer>>(interval) {
            @Override
            public long extractTimestamp(Tuple3<String, Long, Integer> element) {
                return 0;
            }
        });

这里又分为自定义和系统自带,一般情况下使用系统的。

延迟数据处理

对于上述提到的案例,我们稍微修改下参数,这里改为2
在这里插入图片描述
那么现在就变成允许2秒延迟数据再触发窗口计算,如下:
在这里插入图片描述
如图,这里任然是5秒一个窗口,计算[0,5000)区间内数据,这个时候允许延迟2秒数据,则数据流中最大时间戳 >= 窗口结束时间 + 允许延迟时间触发窗口计算,统计[0,5000)区间内数据。这个时候(3000,a,1)这条数据虽然晚到,但也能被处理计算。
这里总结下窗口触发条件:
watermark >= 窗口结束时间
watermark = 当前时间戳最大时间 - 允许延迟时间
如果有一种情况,当(3000,a,1)这条数据,在2秒延迟以外到达,这个时候窗口已经触发计算完了,窗口已经关闭,那么这个时候延迟的数据怎么处理呢?flink给我一个策略:SideOutputTag又叫侧数据流,他可以把延迟数据搜集起来。

public static void test2(StreamExecutionEnvironment env){
        OutputTag<Tuple2<String, Integer>> outputTag = new OutputTag<Tuple2<String, Integer>>("late-data") {};
        DataStreamSource<String> stream = env.socketTextStream("localhost", 9527);
        SingleOutputStreamOperator<Tuple2<String, Integer>> window_stream =
                stream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(2)) {
            @Override
            public long extractTimestamp(String s) {
                return Long.parseLong(s.split(",")[0]);
            }
        })
                .map(new MapFunction<String, Tuple2<String, Integer>>() {
                    @Override
                    public Tuple2<String, Integer> map(String s) throws Exception {
                        String[] splits = s.split(",");
                        return Tuple2.of(splits[1], Integer.parseInt(splits[2]));
                    }
                })
                .keyBy(x -> x.f0)
                .window(TumblingEventTimeWindows.of(Time.seconds(6))) //滚动窗口
                .sideOutputLateData(outputTag)//延迟数据放入侧输出流里
                .allowedLateness(Time.seconds(3))//最大允许延迟3秒
                .sideOutputLateData(outputTag)//延迟之外的数据保存在侧输出流
                .sum(1);

        window_stream.print();
        DataStream<Tuple2<String, Integer>> sideOutput = window_stream.getSideOutput(outputTag);
        sideOutput.printToErr();

    }

这里是watermark+allowedLateness+sideOutputLateData的一个综合案例,接下来输入以下数据进行分析:
1000,a,1
3000,a,1
2000,a,1
5999,a,1
7000,a,1
7999,a,1
2999,a,1
4999,a,1
11000,a,1
1999,a,1
在这里插入图片描述
先说下代码里的配置:
watermark = 2s 、 window = 6s、 allowedLateness = 3s
这里我第一条数据输入(1000,a,1),那么第一个窗口会计算[0,6000)。需要说明的是窗口的起点是0,而不是1000,是因为在TumblingEventTimeWindows里设置只有windowSize,而没有offset。所以这边默认从0开始,下面是源码。在这里插入图片描述
然后回到输入的数据流里,因为watermark设置是2秒,所以当数据流时间戳达到2+6=8秒时,即(7999,a,1)这条数据到来,会触发[0,6000)分区的窗口计算。这是会计算(1000,a,1),(3000,a,1),(2000,a,1),(5999,a,1)的求和结果,即(a,4)。
这个时候再输入(2999,a,1),(4999,a,1),但由于前面配置了allowedLateness等于3秒,所以第一个窗口会在2+6+3=11秒后关闭,再输入(11000,a,1)触发触发第一个窗口计算,会在累加(2999,a,1),(4999,a,1)求和的结果,即(a,6)。计算完毕并关闭窗口。最后再来一条(1999,a,1)数据,因为第一个窗口已经关闭,此时数据保存在outputTag侧输出流里,通过getSideOutput打印到控制台。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值