Flink原理:这一次带你彻底搞懂watermark

背景

我们知道,流处理从事件产生,到流经source,再到operator,中间是有一个过程和时间的。虽然大部分情况下,流到operator的数据都是按照事件产生的时间顺序来的,但是也不排除由于网络延迟等原因,导致乱序的产生,特别是使用kafka的话,多个分区的数据无法保证有序。那么此时出现一个问题,一旦出现乱序,如果只根据 eventTime 决定 window 的运行,我们不能明确数据是否全部到位,又不能无限期的等下去,必须要有个机制来保证一个特定的时间后,必须触发window去进行计算了。这个特别的机制,就是watermark
在这里插入图片描述

图1:数据流入图

如图中的 record 3 和 record 5 为乱序数据,record 4 为迟到数据,下文会介绍 Flink 是如何处理迟到数据的。

定义

watermark是一种特殊的时间戳,也是一种被插入到数据流的特殊的数据结构,用于表示eventTime小于watermark的事件已经全部落入到相应的窗口中,此时可进行窗口操作。
在这里插入图片描述

图2:数据流入图

在这里插入图片描述

图3:数据落位图

如图是一个乱序流,窗口大小为5。
w(5)表示eventTime < 5的所有数据均已落入相应窗口,window_end_time < =5的所有窗口都将进行计算;
w(10)表示表示eventTime < 10的所有数据均已落入相应窗口,5 < window_end_time < =10的所有窗口都将进行计算。

生成

1. 生成时机

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

2. 生成方式
  1. With Periodic Watermarks(常用)

周期性的生成watermark,周期默认是200ms,可通过env.getConfig().setAutoWatermarkInterval()进行修改。这种watermark生成方式需要实现AssignerWithPeriodicWatermarks接口,代码如下:

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的逻辑
             * 默认200ms被调用一次
             */
            @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);
                return timestamp;
            }
        });
  1. With Punctuated Watermarks(不常用)

在满足自定义条件时生成watermark,每一个元素都有机会判断是否生成一个watermark。 如果得到的watermark 不为null并且比之前的大就注入流中。这种watermark生成方式需要实现AssignerWithPunctuatedWatermarks接口,使用方式如下:

DataStream<Tuple2<String, Long>> waterMarkStream = inputMap.assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks<Tuple2<String, Long>>(){
   

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

            @Nullable
            @Override
            public Watermark checkAndGetNextWatermark(Tuple2<String, Long> lastElement, long extractedTimestamp) {
   
                // 当时间戳为偶数则生成,为奇数不不生成
                return lastElement.f1 % 2 == 0 ? new Watermark(extractedTimestamp) : null;
            }
        });

更新规则

1. 单并行度

watermark单调递增,一直覆盖较小的watermark

2. 多并行度

每个分区都会维护和更新自己的watermark。某一时刻的watermark取所有分区中最小的那一个,详情见watermark的传播

传播

Tasks 内部有一个 time services,维护 timers ,当接收到 watermark 时触发。例如,一个窗口 operator 为每一个活跃窗口在 time servive 注册一个 timer,当event time大于窗口结束时间时,清除窗口状态。

当 task 接收到 watermark 后,会执行以下操作:

  1. task 根据 watermark 的时间戳,更新内部的 event_time clock。
  2. time service 区分出所有时间戳小于更新之后的 event_time 的 timers,对超时的 timer,task 执行回调函数触发计算并发射数据。
  3. task 发射 watermark,时间戳为更新之后的 event_time。
    在这里插入图片描述
图4:watermark传播

窗口触发时机分析

下面以一些实验对窗口的触发时机进行分析

1. 示例一
public class BoundedOutOfOrdernessGenerator implements AssignerWithPeriodicWatermarks<MyEvent> {
   

    private final long maxOutOfOrderness = 3000; // 3.0 seconds
  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值