理解Flink中的watermark

转载自hongyuzhou的博客

1. Watermark 的理解

最早看到 Watermark 的概念就是在 Flink 的官方文档里面:

The mechanism in Flink to measure progress in event time is watermarks. Watermarks flow as part of the data stream and carry a timestamp t. A Watermark(t) declares that event time has reached time t in that stream, meaning that there should be no more elements from the stream with a timestamp t’ <= t (i.e. events with timestamps older or equal to the watermark)

上面这句话中有一个词叫做 event-time,了解 DataFlow 模型的同学都知道, event-time 表示的是每个独立事件在各自设备上产生的时间,是这个事件特有的属性,好比是一个人的生日。与之对应的还有 processing-time 和 ingestion-time。理解起来也不难,processing-time 就是 Flink 的 window 操作该事件的时间,ingestion-time 是事件作为数据源进入 Flink 系统的时间。参考下面这张图
在这里插入图片描述

重新回到引用中的这句英文,翻译过来的大致意思是," Watermarks 是 Flink 用来度量 event-time 事件处理进度的一种机制,Watermarks 作为数据流的一部分,携带一个时间 t。表示这次的数据流中 event-time 已经处理到时间 t 了,所有 event-time 早于或等于 t 时刻的事件都不应该再出现在这个数据流中了。" 有些朋友可能会有疑问,为什么要弄这么一个奇怪的限制。这是因为一般来说,在实际环境下,由于网络阻塞,延迟等问题,processing-time 相比 event-time 会有延迟滞后的现象,而且这种现象非常普遍。也就是说,并不是生产出一个事件, 就能在第一时间被处理。参考下面这张图,来源于 Dataflow Model 论文。
时间域的偏离
图2可以看到,横坐标是 event-time,纵坐标是 processing-time。理想的处理机制是,12:01分的 event-time 时间,应该就在 12:01分的 processing-time 时刻被处理。但是实际情况下,横向蓝色实线对应的 processing-time 晚于12:01分。所以图中横向黑色实线表示 event-time 的偏离,纵向红色实线表示 processing-time 的偏离,这些都比较容易理解。 接下来,按照原图的注释,浅色虚线表示 理想的 watermark,深色虚线表示 实际的 watermark。看到这里似乎有些困惑,该如何理解呢?

直到,我看到另外一篇由 Tyler Akidau 大神写的文章 Stream 102,是这样描述 watermark 的:

Watermarks are temporal notions of input completeness in the event-time domain. Worded differently, they are the way the system measures progress and completeness relative to the event times of the records being processed in a stream of events.

大致意思是," Watermark 是从 event-time 维度描述输入数据的完整性, 换句话说,是系统评价数据流中处理 event-time 事件进度和完整性的方式"。在图2中,随着 processing-time 的推移,深色虚线逐步获取了 event-time 的完成进度。从概念上说,可以理解成一个函数F§ -> E,给定一个 processing-time 值,函数返回对应的 event-time 值。这个 event-time 时间值 E 就被系统认为所有早于 E 时刻的 event-time 事件都已经被观察处理过,或者说系统断言,以后都不会再有早于 E 时刻的 event-time 事件出现了。至此,对于流处理的算子(window … )而言,对于这个时间段的计算告一段落,可以生成结果了。

总结:

  • Watermark是一种告诉Flink一个消息延迟多少的方式。它定义了什么时候不再等待更早的数据。

  • 可以把Watermarks理解为一个水位线,这个Watermarks在不断的变化。Watermark实际上作为数据流的一部分随数据流流动。

  • 当Flink中的运算符接收到Watermarks时,它明白早于该时间的消息已经完全抵达计算引擎,即假设不会再有时间小于水位线的事件到达。

  • 这个假设是触发窗口计算的基础,只有水位线越过窗口对应的结束时间,窗口才会关闭和进行计算。(当窗口进行计算或关闭后,即使有属于窗口内的事件"姗姗来迟",也不会影响窗口的计算结果,这部分结果可以输出到flink的旁路输出做其他处理)

2. Watermark 的例子

了解了 watermark 概念,引用 vishnuviswanath 论坛上的例子更好的加深理解。

case 1: 消息到达没有延迟
如图,假设数据源生成3条消息,分别是第13秒,第13秒,第16秒。计算窗口为10秒,每隔5秒滑动一次。
在这里插入图片描述这些消息会如图落入对应时间窗口。前两个在第13秒生成的消息会落入 [5s - 15s] 的窗口1和 [10s - 20s] 的窗口2。而第16秒生成的消息 会落入 [10s - 20s] 的窗口2和 [15s - 25s] 的窗口3。最终每隔窗口期望得到的结果分别是 (a,2), (a,3) 和 (a,1)。
在这里插入图片描述

case 2: 消息到达存在延迟
假设有一个第13秒生成的消息到达时,延迟了6秒(在第19秒到达),原因可能是网络阻塞。那么情况就变成了下图这样。
在这里插入图片描述

图中可以看到,延迟的消息落入了窗口2和窗口3,因为19秒属于 10-20 和 15-25的范围内。虽然这个例子中,对于窗口2的结果没有影响,但是窗口1和窗口3的结果都发生了变化,导致发生错误的原因就是处理消息时间的时候用的是 processing-time 而不是 event-time。

case 3:用基于 event-time 的系统来处理问题
使用 event-time 处理机制,需要对每个消息提取 event-time 信息。这个时间信息一般消息自带,或者手动生成。看下面一个例子,暂时忽略 getCurrentWatermark 这个函数,之后来讨论。

public class TimestampExtractor implements AssignerWithPeriodicWatermarks<String> {
    @Nullable
    @Override
    public Watermark getCurrentWatermark() {
        return new Watermark(System.currentTimeMillis());
    }

    @Override
    public long extractTimestamp(String e, long previousElementTimestamp) {
        // 使用事件中自带的生成时间代替事件的到达时间进行处理
        return Long.parseLong(e.split(",")[1]);
    }
}

有了 event-time 信息提取器之后,主函数流程如下:

public static void main(String[] args) throws Exception {
        final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        DataStream<String> text = env.socketTextStream("localhost", 9999)
                                     .assignTimestampsAndWatermarks(new TimestampExtractor());

        DataStream<Tuple2<String, Integer>> count = text.map((MapFunction<String, Tuple2<String, Integer>>) m -> Tuple2.of(m.split(",")[0], 1))
                                                        .keyBy(0)
                                                        .timeWindow(Time.seconds(10), Time.seconds(5))
                                                        .sum(1);
        count.print();
        env.execute("EventTime processing example");
}

产生的结果如下图,图中可以看出窗口2和窗口3产生了正确的结果,但是窗口1还是错的。Flink 不把延迟的消息指派给窗口3是因为它知道消息的 event-time不属于窗口3。但是为什么 Flink 不将延迟消息指派给窗口1呢?是因为,当延迟消息到达的时候(第19秒)窗口1已经完成计算了。那好,接下来解决这个问题。
在这里插入图片描述

case 4:使用 Watermark 机制
对于我们的案例来说,我们的目的是告诉 Flink 一个消息可以延迟多久。在 case 3 中提到了 getCurrentWatermark 函数,我们设置了 watermark 值为当前系统时间,这表示没有考虑到延迟的消息。现在设置 watermark 值为 当前时间 - 5秒,这就表明 Flink 期望的消息延迟最大是5秒。这是因为窗口在输出最终计算结果的条件是,当 watermark 超过窗口结束时间。所以只有当前时间到达第20秒的时候,窗口1 [5s - 15s] 才符合输出条件,同样的窗口2 [10s - 20s] 要第25秒时才会输出结果。代码如下:

public Watermark getCurrentWatermark() {
        return new Watermark(System.currentTimeMillis() - 5000);
}

运行的结果如下图,最终,三个窗口的结果都正确。
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值