7. Watermark

一、引入:乱序数据

image-20210216185852586

​ 当 Flink 使用 Event Time 模式处理数据流时,它会根据数据里的时间戳来处理基于时间的算子,这样就会由于网络、分布式等原因,|导致数据乱序。如多个并行度从kafka读取数据然后做keyBy操作,虽然kafka在一个分区内部的数据是有序的,但是因为keyBy的下游算子接收到的可能是多个分区的数据,这些数据是没有顺序保证的,那么我们该如何衡量事件时间已经进展到哪一步了呢?Flink提供了一种机制——Watermark

二、Watermark概念和原理

  • Watermark 是一种衡量 Event Time 进展的机制,可以设定延迟触发。

  • 数据流中的 Watermark 用于表示 timestamp 小于 Watermark 的数据都已经到达了,因此,window的执行也是由 Watermark 触发的。

  • Watermark 用来让程序自己平衡延迟和结果的正确性。

    根据第一条定义,Wartermark 衡量 Event Time 的进展,即是可以起到 “调慢事件时间” 的作用,让程序误以为没有到达触发的事件。

    如上述案例:如果把Watermark的延迟调为 3,那么当 1 到达时,Watermark 为 -2,4到达时,Watermark 为 1(表示 1 以前的数据都到达了),5 到达时,Watermark 为 2(表示 2 以前的数据都到达了),知道 Event Time 为 8 的记录到达时,才表示 5 以前的数据都到达,关闭第一个窗口 [1, 5)。

三、Watermark在任务间的传递

image-20210216191938657

​ 一个 subtask 可能接收到多个上游的 Watermark,它将这些 Watermark 存放到对应的分区中,并且向下游发送最小的 Watermark。

四、Watermark空闲问题

如果输入的数据源的某一个分区在一段时间内不携带事件,这意味着 WatermarkGenerator 也不会获得任何作为水印基础的新信息。我们称之为空闲输入或空闲源。这是一个问题,因为其它分区可能仍然携带事件。在这种情况下,任务将会被阻塞,因为wartermark是从所有分区中取最小值,这些非空闲的分区来的数据将都被缓存起来。

为了解决这个问题,Flink的WatermarkStrategy 提供了检查和标记输入为空闲的机制,用法如下:

WatermarkStrategy
  .forBoundedOutOfOrderness[(Long, String)](Duration.ofSeconds(20))
  .withIdleness(Duration.ofMinutes(1))

五、Wartermark对齐

在上一段中,我们讨论了数据源空闲并且可能阻止增加水印的情况。与之相反的是,某个数据源的数据可能被非常快地处理,从而导致它比其他源相对更快地增加其水印。这本身并不是问题。但是,对于使用水印来发出一些数据的operator来说,这实际上可能会成为一个问题。

在这种情况下,与空闲源相反,此类下游运算符(如聚合上的窗口连接)的水印可以继续进行。但是,这些oprator必须在状态中缓存来自快速输入的过量数据,等待其他慢的数据源的水印就绪后才能把这些数据发送出去。这可能导致算子状态的不可控制的增长。

为了解决这个问题,Flink引入了水印对齐,这将确保没有数据源的水印将增加得远远超出其他部分。为了实现对齐,Flink 将暂停那些声称太远的水印的源/任务的消费。与此同时,它将继续从其他源/任务读取记录,这可以将组合水印向前移动,从而解锁更快的水印。

使用方法如下

WatermarkStrategy
        .<Tuple2<Long, String>>forBoundedOutOfOrderness(Duration.ofSeconds(20))
        .withWatermarkAlignment("alignment-group-1", Duration.ofSeconds(20), Duration.ofSeconds(1));

参数解释:

第一个:源应该属于哪个组。用一个标签(例如alignment-group-1)来将共享它的所有源绑定在一起。

第二个:该组的所有源的当前最小水印的最大漂移;

第三个:最大水印应该更新的频率。频繁更新的缺点是,TM 和 JM 之间会传输更多 RPC 消息。

六、代码示例

public class WindowExample {

    /**
     * 两个数据流集合,对相同key进行内联,分配到同一个窗口下,合并并打印
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
        //watermark 自动添加水印调度时间
        //env.getConfig().setAutoWatermarkInterval(200);

        List<Tuple3<String, String, Integer>> tuple3List1 = Arrays.asList(
                new Tuple3<>("A", "M", 10),
                new Tuple3<>("A", "M2", 10),
                new Tuple3<>("B", "M", 2),
                new Tuple3<>("C", "M", 3),
                new Tuple3<>("D", "M", 3)
        );

        List<Tuple3<String, String, Integer>> tuple3List2 = Arrays.asList(
                new Tuple3<>("伍七", "girl", 18),
                new Tuple3<>("吴八", "man", 30),
                new Tuple3<>("A", "N", 1000),
                new Tuple3<>("B", "N", 200),
                new Tuple3<>("C", "N", 300)
        );
        //Datastream 1
        DataStream<Tuple3<String, String, Integer>> dataStream1 = env.fromCollection(tuple3List1)
                .assignTimestampsAndWatermarks(WatermarkStrategy
                        .<Tuple3<String, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(2))
                        .withTimestampAssigner((element, timestamp) -> System.currentTimeMillis()));
//        DataStream<Tuple3<String, String, Integer>> dataStream1 = env.fromCollection(tuple3List1);
        //Datastream 2
        DataStream<Tuple3<String, String, Integer>> dataStream2 = env.fromCollection(tuple3List2)
                //添加水印窗口,如果不添加,则时间窗口会一直等待水印事件时间,不会执行apply
                .assignTimestampsAndWatermarks(WatermarkStrategy
                        .<Tuple3<String, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(2))
                        .withTimestampAssigner(new SerializableTimestampAssigner<Tuple3<String, String, Integer>>() {
                            @Override
                            public long extractTimestamp(Tuple3<String, String, Integer> element, long timestamp) {
                                return System.currentTimeMillis();
                            }
                        })
                );

        DataStream<Tuple3<String, String, Integer>> dataStream3 = dataStream1.union(dataStream2);

        //对dataStream1和dataStream2两个数据流进行关联,没有关联也保留
        //Datastream 3
        SingleOutputStreamOperator<Tuple3<String, String, Integer>> newDataStream = dataStream3
                .keyBy(f -> f.f0)
                .window(TumblingEventTimeWindows.of(Time.seconds(1)))
//                对于从集合中加载到流中的case,在程序中会很快,所以要把时间窗口设置小
//                .window(TumblingProcessingTimeWindows.of(Time.milliseconds(5L)))
//                .window(TumblingProcessingTimeWindows.of(Time.minutes(1L)))
                .reduce(new ReduceFunction<Tuple3<String, String, Integer>>() {
                    @Override
                    public Tuple3<String, String, Integer> reduce(Tuple3<String, String, Integer> value1, Tuple3<String, String, Integer> value2) throws Exception {
                        return new Tuple3<>(value1.f0, value1.f1 + value2.f1, value1.f2 + value2.f2);
                    }
                });

        newDataStream.print();

        env.execute("flink CoGroup job");
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值