Flink最佳实践 - Watermark原理及实践问题解析

原文链接: Flink最佳实践 - Watermark原理及实践问题解析 - Liebing’s Homepage

Watermark在Google的The Dataflow Model论文中被首次提出, 它在基于Event Time的流处理中具有重要作用, 是一种平衡计算结果准确性和延迟的机制. 虽然Watermark的概念不难理解, Flink中也有完善的Watermark策略, 但是在实际场景中生成合理的Watermark却并非那么简单, 在并行流下更是可能会出现多种问题.

本文在简单介绍Watermark的背景及概念之后, 详细介绍Flink在DataStream API和SQL API中对Watermark的支持, 接着解析在并行流下Watermark可能产生的一些问题, 最后通过一个具体案例介绍如何生成合理的Watermark.

Watermark背景

上文也说到了Watermark是在The Dataflow Model论文中提出的, 事实上The Dataflow Model对流处理来说是具有划时代意义的. 在Dataflow Model提出之前, 由于缺乏相关的理论指导, 开源的流处理引擎大多只能支持近似计算. 因此, 在当时Lambda架构是一种普遍的选择, 即采用两条数据处理链路, 使用批处理引擎产生准确但延迟高的结果, 使用流处理引擎产生低延迟但近似的结果. Dataflow Model通过引入Time Domain, Watermark, Window等一系列概念, 在理论上统一了流处理与批处理, 认为批处理是流处理的一种特例, 通过一些机制时可以实现准确且低延迟的流处理的. 在Streaming 101中作者也说到了设计良好的流系统实际上提供了严格的批处理功能超集.

关于Watermark与流处理更详细的背景可以阅读深入理解流计算中的 Watermark, 本文不再过多赘述.

Watermark概念

关于Watermark的概念, Flink文档中的描述比较容易理解, 以下是文档中的原话.

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).

简单来说, 一个Watermark就是一个标识, 一个时间戳为 t t t的Watermark表示Event Time小于或等于 t t t的事件都已经到达. 有了这个前提, 基于Event Time的窗口计算才能产生准确的结果, 例如, 如果一个时间窗口的结束时间为 t 0 t_0 t0, 当前已经产生的最大Watermark为 t 1 t_1 t1, 并且 t 1 > t 0 t_1 > t_0 t1>t0, 那么现在触发该窗口的计算可以得到准确的结果, 因为属于该窗口的数据都已经到达. 为了更好地理解Watermark的含义, 这里举两个具体的例子.

下图(来自Flink文档)是带有时间戳的事件流, 由于事件是严格按顺序到达的, 因此Watermark的生成就非常容易, 只需要周期性地将当前某个事件携带的时间戳作为Watermark即可.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hAegTFZT-1651309018768)(https://cdn.nlark.com/yuque/0/2022/svg/1514427/1650877473294-915f5714-8c3c-4d1c-a5a4-cb11d880e920.svg#clientId=u2963eb5d-97ce-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=145&id=u5ff960e8&margin=%5Bobject%20Object%5D&originHeight=157&originWidth=534&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=ud11fc191-7c0c-453a-b956-c05cfab0356&title=&width=492)]

在严格按时间顺序到达的事件流中生成Watermark十分简单, 但是现实场景中的事件往往是乱序点的, 如下图所示. 对于这种情况, 如何生成Watermark就需要多方权衡了.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KlXQ4i7D-1651309018770)(https://cdn.nlark.com/yuque/0/2022/svg/1514427/1650877530959-16add6e0-fe75-4cd0-ba12-7b46703ca954.svg#clientId=u2963eb5d-97ce-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=145&id=uddb439eb&margin=%5Bobject%20Object%5D&originHeight=157&originWidth=534&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u0fafab79-385b-4899-9254-3e527b5184e&title=&width=492)]

进一步思考其实可以发现, 之所以需要Watermark是因为在现实中Event Time总是滞后于Processing Time的, 这是由于现实中的数据源在地理上分散, 且其所处的硬件和网络环境各异, 将数据传输到处理中心存在延迟, 且可能各个数据源的延迟各不相同, 这就进一步导致了数据乱序. 如果Processing Time和Event Time总是同步, 那么处理时也就不会有乱序数据, 也就不需要Watermark.

通过下图我们可以进一步理解Event Time, Processing Time, Watermark与数据乱序之间的关系.

  • 首先从图(a)中我们可以明确以下内容:
    • 理想状态下数据一旦产生立即被处理, 即Event Time和Processing Time之间不存在偏差, 也就是图中斜率为1虚线展示的情况, 它表示Event Time总是等于Processing Time.
    • 真实状态下由于处理管道引入的随机延时, Processing Time总是滞后于Event Time, 也就是图中红色曲线展示的情况, 它表示Processing Time总是大于Event Time.
    • 曲线与虚线在纵轴方向的差值为Processing Time Lag, 也就是处理管道引入的时间误差, 以图中的状态为例, 发生在t1时刻的事件由于管道误差, 直到t2(t2>t1)时刻才被处理, 纵轴方向上t2-t1的值就称为Processing Time Lag.
    • 曲线与虚线在横轴方向的差值为Event Time Skew, 可以理解为由于管道误差而使得计算引擎观察到的数据存在滞后性, 以图中的状态为例, 在t2时刻只能观察到t2-t1时刻以前的数据, 横轴方向上t2-t1的值就称为Event Time Skew.
  • 明确以上概念后我们再来看图(b):
    • 图中有三个时间窗口[t0, t1), [t1, t2), [t2, t3), 蓝色横线是Watermark, 这里取目前为止观察到的最大事件时间戳为Watermark.
    • 图中圆点表示事件. A和B, C和D之间出现了数据乱序, 因为A的Event Time小于B, 但A的Processing Time大于B, 也就是说A比B先发生, 但是达到处理引擎的时间比B晚. C和D之间同理. 不过A和B, C和D之间的数据乱序并不影响窗口计算的准确性.
    • 图中E也是一个乱序点, 它属于第二个窗口, 但是在大于t2的Watermark生成之前E并未到达, 也就是说E是迟到数据, 在E到达之前窗口已经触发计算. 这里也可以看出在乱序情况下取当前最观察到的最大时间戳为Watermark并不是完美的方案. 最简单的解决方案是增大Event Time Skew, 我们可以将当前观察到的最大事件时间戳减去一个固定值作为Watermark, 如图中黄色虚线所示, 若当前观察到的最大时间戳为t2, 那么我们将t’作为Watermark, 由于t’小于E的事件时间, 因此在E到达时窗口还未触发计算, 就能更大程度地容忍乱序.

![blogs_pics.png](https://img-blog.csdnimg.cn/img_convert/8217f5d369d2043df6ab30ecbc808467.png#clientId=u03041fc5-0553-4&crop=0&crop=0&crop=1&crop=1&from=ui&height=316&id=uf09f48a8&margin=[object Object]&name=blogs_pics.png&originHeight=2488&originWidth=5421&originalType=binary&ratio=1&rotation=0&showTitle=false&size=286155&status=done&style=none&taskId=u4ca7cb09-cf88-480f-b0ec-33ecbc423ca&title=&width=688)

从以上案例也可以发现, 为实现中的数据流生成Watermark并不简单. 最简单的方法是以当前观察到的最大时间戳减去一个固定值为Watermark, 这也是Flink内置的BoundedOutOfOrdernessWatermarks策略的实现原理. 至于这个固定值是多少, 只能通过观察具体应用的数据延迟状况来设定了. 如果这个值设置的太大, 那么虽然避免了数据迟到, 但是却增加了窗口触发计算的延迟, 如果设置得太小又会导致窗口过早触发计算, 从而使得结果不准确. 这也可以看出Watermark可以用来平衡计算结果的准确性和延迟.

如果从Watermark的角度来审视一下批处理和流处理的关系我们可以发现: 在批处理中我们实际上是使用了非常宽松的Watermark策略, 比如我们通常会在当天处理前一天的数据, 这样的Watermark策略可以理解为将当前观察到的数据的最大时间戳减去数个小时作为Watermark. 在这样宽松的Watermark策略下, 总能保证在批处理程序启动时所有数据已经全部到达, 因此产生准确的结果. 而在流处理中我们通常会使用紧迫的Watermark策略, 以更快得到处理结果, 这在降低延迟的情况下可能牺牲一定的准确性. 从中也可以看出, Watermark是统一流处理与批处理的重要理论依据.

实际上, 如果把Watermark生成抽象为一个算法, 那么其输入就是当前已经观察到的数据, 输出就是Watermark. 关键在于如何根据观察到的数据生成更好的Watermark, 尽可能使得计算结果准确并降低延迟. 本文后面会根据一个具体的案例, 来分析如何根据现实的数据情况生成相对合理的Watermark.

Flink中的Watermark

Flink DataStream中的Watermark

Flink的DataStream API提供了对Watermark的完善支持, 不仅内置了一些常用的Watermark生成器, 也提供了扩展接口, 用户可实现自己的Watermark生成算法.

Watermark的生成

Flink 1.11中对Watermark相关的API进行了重构, 具体可见FLIP-126. 由于旧API已经废弃, 且新API提供了更好的抽象, 本文只讲述新API的使用方法.

新API提供了WatermarkStrategy接口, 用于组装WaterMarkGenerator(用于实现Watermark生成算法)和TimestampAssigner(用于指示如何从事件中提取事件时间). 在DataStream API中有两种使用WatermarkStrategy的方法: 1) 直接在Source中给出; 2) 调用DataStreamassignTimestampsAndWatermarks方法. 具体使用方式如下.

final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvi
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值