流式计算中的 Window 机制 | 青训营笔记

78 阅读9分钟

这是我参与「第四届青训营 」笔记创作活动的第11天!

一、概述。

1.流式计算VS批式计算:

8f2751e1ac9fe2c3cfe36769d286a9c.jpg

2.批处理:

批处理模型典型的数仓架构为T+1架构,即数据计算时天级别的,当天只能看到前一天的计算结果

通常使用的计算引擎为Hive或者Spark等。计算的时候,数据是完全ready的,输入和输出都是确定性的

3.处理时间窗口:
  • 实时计算:处理时间窗口
  • 数据实时流动,实时计算,窗口结束直接发送结果,不需要周期调度任务
4.处理时间VS事件时间:
  • 处理时间(Processing time) :数据在流式计算系统中真正处理时所在机器的当前时间。(处理时间这是在系统中观察事件的时间)
  • 事件时间(Event time) :数据产生的时间,比如客户端、传感器、后端代码等上报数据时的时间。(事件时间是事件实际发生的时间)

0233413314f2a3699d0f61384566564.jpg

5.事件时间窗口:

实时计算:事件时间窗口(传入,运算,到输出时间总和)。

数据实时进入到真实事件发生的窗口中进行计算,可以有效的处理数据延迟和乱序。

05f0ae5ad6589e086c84c9a3fcb5b50.jpg

6.Watermark:
  • 窗口真实结束时间(延迟与乱序)衡量:watermark
  • 在数据中插入—些watermark,来表示当前的真实时间。
  • 在数据存在乱序的时候,watermark就比较重要了,它可以用来在乱序容忍和实时性之间做一个平衡。

5bb6b7eba597014f42b8afbf562f42b.jpg

fa1c916f43f9b698ff513b5842a5233.jpg

二、Watermark。

1.什么是Watermark?
  • 表示系统认为的当前真实的事件时间
2.如何产生Watermark?

可以通过Watermark Generator 来生成。

一般是从数据的事件时间来产生,产生策略可以灵活多样,最常见的包括使用当前事件时间的时间减去一个固定的delay(下列-5s,-20s,即延迟),来表示可以可以容忍多长时间的乱序。

SQL: 1c80ef5800862d659d2c261c754027f.jpg DataStream:

4279564906eb91db6cbe71145b1c252.jpg

3.如何传递Watermark?

每个算子根据上游传递watermark最小值作为自己的数值(依次递减(类Checkpoint快照传递)),同时还要给下游传播数值。 7d63bb2564bef9b2f231fb33bb8c4a4.jpg

4.如何通过Flink UI 观察 Watermark?
  • 一般通过Flink Web UI上的信息来观察当前任务的watermark情况
  • (没有输出) ,是生产实践中最容易遇到的问题,开发事件时间的窗口任务的时候,经常会忘记了设置watermark,或者数据太少,watermark没有及时的更新,导致窗口一直不能触发
5.典型问题:

Per-partition(分区) Vs per-subtask(子任务) watermark生成

  • Per-subtask watermark生成(子任务)
    • 早期版本都是这种机制。典型的问题是如果一个source subtask(源任务)消费多个partition(下游的区),那么多个partition之间的数据读取可能会加剧乱序程度。
  • Per-partition watermark生成(分区)
    • 新版本引入了基于每个 partition单独的watermark生成机制,这种机制可以有效避免上面的问题。

部分partition/subtask断流

  • 根据上面提到的watermark传递机制,下游subtask 会将上游所有subtask的watermark值的最小值作为自身的watermark值。如果上游有一个subtask的 watermark不更新了,则下游的watermark都不更新。
  • 解决方案:ldle source(空闲)
    • 当某个subtask断流超过配置的idle超时时间时,将当前subtask置为idle,并下发一个idle的状态给下游。下游在计算自身watermark的时候,可以忽略掉当前是idle的那些subtask(子任务)。

迟到数据处理

因为watermark表示当前事件发生的真实时间,那晚于watermark的数据到来时,系统会认为这种数据是迟到的数据。

  • 算子自身来决定如何处理迟到数据:
    • Window聚合,默认会丢弃迟到数
    • 双流join,如果是outer join,则可以认为它不能join到任何数据
    • CEP,默认丢弃

三、Window-基本功能。

1.Window分类:
  • 典型的Window:
  1. Tumble Window(滚动窗口)
  2. Sliding Window(滑动窗口)
  3. Session Window(会话窗口)
  • 其它Window:
  1. 全局Window
  2. Count Window
  3. 累计窗口 等
Window使用:
  • API应用程序编程接口,抽象分层
  • 抽象程度越高,用户使用成本低,表达能力低(有限)

1e2d43afcf77b2adaba568ff2d09e48.jpg

滚动窗口:
  • 窗口划分:
  • 1.每个key单独划分
  • 2.每条数据只会属于一个窗口

窗口触发: Window结束时间到达的时候一次性触发。 6fc37b36c5dbdb000d60fe013aa9352.jpg

滑动窗口:
  • 窗口划分:
  • 1.每个key单独划分
  • 2.每条数据可能会属于多个窗口

窗口触发: Window结束时间到达的时候一次性触发。

321e30aa4b39daef9d2da9c549d3ad3.jpg

全话窗口:

窗口划分:

  • 1.每个key单独划分
  • 2.每条数据会单独划分为一个窗口,如果window之间有交集,则会对窗口进行merge(合并)

窗口触发: Window结束时间到达的时候一次性触发。

9499715554d3a71b6c0dd8e863e235d.jpg

迟到数据处理:

怎样定义迟到数据?

迟到数据定义:

  • 一条数据到来后,会用WindowAssigner 给它划分一个 window(start,end),一般时间窗口是一个时间区间,比如[10:00,11:00),如果划分出来的 window end 比当前的 watermark 值还小,说明这个窗口已经触发了计算了,这条数据会被认为是迟到数据。

什么情况下会产生迟到数据?

迟到数据产生:

  • 只有事件时间,(实际时间)下才会有迟到的数据。

迟到数据默认处理?

  • 丢掉

1.Allow lateness:

  • 这种方式需要设置一个允许迟到的时间。设置之后,窗口正常计算结束后,不会马上清理状态,而是会多保留 allowLateness 这么长时间,在这段时间内如果还有数据到来,则继续之前(先retreat回滚,然后在缓存区继续,参考快照)的状态进行计算。
  • 适用于:DataStream、SQL

2.SideOutput(侧输出流):

  • 这种方式需要对迟到数据打一个 tag,然后在 DataStream 上根据这个 tag 获取到迟到数据流,然后业务层面自行选择进行处理。
  • 适用于:DataStream

403e60904d4e1930d8631281acd2300.jpg

增量VS全量计算:

增量计算(更优,平缓,保存少,只保存中间结果):

  • 每条数据到来,直接进行计算,window只存储计算结果。 比如计算sum,状态中只需要存储sum的结果,不需要保存每条数据。
  • 典型的reduce、aggregate等函数都是增量计算
  • SQL中的聚合只有增量计算

b7b7d9b3b4053d0f877a012fd2af478.jpg 全量计算(保存全体数据,大量缓存):

  • 每条数据到来,会存储到window的state中。等到window触发计算的时候,将所有数据(缓存,数据量,数据类型)拿出来一起计算。
  • 典型的process函数就是全量计算

4ec6a111d494c9c939bf9db3793c88e.jpg

EMIT触发:

什么叫EMIT?

  • 通常来讲,Window 都是在结束的时候才能输出结果,比如 1h 的 tumble window,只有在 1 个小时结束的时候才能统一输出结果。
  • 如果窗口比较大,比如 1h 或者 1 天,甚至于更大的话,那计算结果输出的延迟就比较高,失去了实时计算的意义。
  • EMIT 输出指的是,在 window 没有结束的时候,提前把 window 计算的部分结果输出出来。

怎么实现?

在DataStream里面可以通过自定义Trigger来实现,Trigger的结果可以是:

  • CONTINUE
  • FIRE(触发计算,但是不清理窗口状态)
  • PURGE
  • FIRE_AND_PURGE

SQL也可以使用,通过配置:

  • table.exec.emit.early-fire.enabled=true
  • table.exec.emit.early-fire.delay=(time)
2.高级优化:
Mini-batch优化:

Mini-batch优化解决频繁访问状态的问题: (多数据批处理)

即攒一小批数据再进行计算,这批数据每个key的state访问只有一次,这样在单个key的数据比较集中的情况下,对于状态访问可以有效的降低频率,最终提升性能。

44c7e6a2f01bdb6fb0000d1931ef51b.jpg

倾斜优化-local-global:
  • local-global优化是分布式系统中典型的优化,主要是可以降低数据shuffle的量,同时也可以缓解数据的倾斜。
  • Local-global优化即将原先的Aggregate分成Local和Global两阶段聚合,即MapReduce模型中Combine+Reduce处理模式。第一阶段在上游节点本地攒一批数据进行聚合(localAgg),并输出这次微批的增量值(Accumulator)。第二阶段再将收到的Accumulator merge起来,得到最终的结果(globalAgg)。 image.png
Distinct计算状态复用:

83c5cdb128b70586c42d7be7faa3b9f.jpg

Pane优化:
  • Pane 优化降低滑动窗口的状态存储量
  • 将窗口的状态划分成更小粒度的pane,最小单位,可组合为窗口
  • 将这个窗口对应的pane的结果merge(合并,归并)起来就可以了

9610f44acb4e1f5514a5e28ae2f29ff.jpg

199f981df294270eb98891df8b679fd.jpg

四、案例分析。

1.需求一:使用Flink SQL 计算抖音的日活曲线 DAU。

10e16ea496a68c8b835a007fe1be266.jpg

b5551dada49d009e34381591ae8efd7.jpg

  • 问题:所有数据都需计算,无法并行。
  • 解决方案:通过两阶段聚合来把数据打散,完成第一轮聚合,第二轮聚合只需要对各个分桶的结果求和即可。
2.需求二:计算大数据任务的资源使用
  • 问题描述:
    • 大数据任务(特指离线任务)运行时通常会有多个 container 启动并运行,每个 container 在运行结束的时候,YARN 会负责将它的资源使用(CPU、内存)情况上报。一般大数据任务运行时间从几分钟到几小时不等。
  • 需求:
    • 根据 YARN 上报的各个 container 的信息,在任务结束的时候,尽快的计算出一个任务运行所消耗的总的资源。 假设前后两个 container 结束时间差不超过 10min

70715888cbc63081f5981fbbd7ee0d0.jpg

  • 典型的可以通过会话窗口来将数据划分到一个window中,然后再将结果求和即可。

总结

本次课程主要学习了流式计算和批式计算的区别、介绍了Watermark的概念以及其如何产生传递等问题、还介绍了三种最基本的window类型以及他们的实现原理等。但这学习过程中对Distinct的状态复用优化不是非常理解。