这是我参与「第四届青训营」笔记创作活动的第4天
本节课程目录:
- 概述流式计算和批式计算
- Watermark 的含义、生成方法、传递机制等
- Window 基本功能和高效优化
- 案例分析
1. 概述
1.1 流式计算 VS 批式计算
实时性越高,数据价值越高
1.2 批处理
批处理模型典型的数仓架构为T+1架构,即数据计算是天级别的,当天只能看到前一天的计算结果。
通常使用的计算引擎为Hive或者Spark等。计算的时候,数据是完全确定的,输入和输出都是确定性的。
1.3 处理时间窗口
实时计算:处理时间窗口
数据实时流动,实时计算,窗口结束直接发送结果,不需要周期调度任务。
1.4 处理时间 VS 事件时间
处理时间:数据在流式计算系统中真正处理时所在机器的当前时间。
事件时间:数据产生的时间,比如客户端、传感器、后段代码等上报数据时的时间。
1.5 事件时间窗口
实时计算:事件时间窗口
数据实时进入到真实事件发生的窗口中进行计算,可以有效的处理数据延迟和乱序。
1.6 Watermark
- 在数据中插入一些 watermark,来表示当前的真实时间。
- 在数据存在乱序的时候,watermark 就比较重要了,它可以用来在乱序容忍和实时性之间做一个平衡。
2. Watermark
2.1 什么是Watermark?
表示当前系统认为的事件时间所在的真实时间
2.2 如何产生 Watermark
一般是从数据的事件时间来产生,产生策略可以灵活多样,最常见的包括使用当前事件时间的时间减去一个固定的 delay,来表示可以容忍多长时间的乱序。
- SQL
- Datastream
2.3 如何传递 Watermark
传递就类似于 Checkpoint 的 barrier,上下游 task 之间有数据传输关系的,上游就会将 watermark 传递给下游;下游收到多个上游传递过来的 watermark 后,默认会取其中最小值来作为自身的 watermark ,同时它也会将自己 watermark 传递给它的下游。经过整个传递过程,最终系统中每一个计算单元就都会实时的知道自身当前的 watermark 是多少。
2.4 如何通过 Flink UI 观察 Watermark
2.5 典型问题
2.5.1 Per-partition VS Per-subtask watermark 生成
- Per-partition watermark 生成
早期版本的机制。典型问题是如果要一个 source subtask 消费多个 partition,那么多个 portition 之间的数据读取可能会加剧乱序程度。
- Per-subtask watermark 生成 新版本引入了基于每个 partition 单独的 watermark 生成机器,而这种机制可有效避免。
2.5.2 部分 partition/subtask 断流
根据上面提到的 watermark 传递机制,下游 subtask 会将上游所有 subtask 的 watermark 值的最小值作为自身的 watermark 值。如果上游有一个 subtask 的watermark 不更新了,则下游的watermark 都不更新。
- 解决方案: 当某个 subtask 断流超过配置的idle超市时间时,将就当前 subtask 置为 idle,并下发一个 idle 的状态给下游。下游在计算自身 watermark 的时候,可以忽略掉当前是 idle 的那些 subtask。
2.5.3 迟到数据处理
因为 watermark 表示当前事件处理发生的真实事件,那晚于 watermark 的数据到来时,系统会认为这种数据时迟到的数据。
算子自身来决定如何处理迟到数据:
- Window 集合,默认会丢弃迟到数据
- 双流 join ,如果是outer join, 则可以认为它不能join任何属于
- CEP,默认丢弃
3. Window
3.1 Window 基本功能
3.1.1 Window 分类
-
TUMBLE Window 滚动窗口
- 最常见的窗口类型,根据数据的时间划分到它所属的窗口中
windowStart = timestamp - timestamp % windowSize,这条数据所属的 window 就是[windowStart, windowStart + windowSize) - Flink 中的窗口划分是 key 级别的
- 每条数据只会属于一个窗口
- 当 window 结束时间到达的时候,窗口一次性触发对应的输出
- 最常见的窗口类型,根据数据的时间划分到它所属的窗口中
-
HOP Window 滑动窗口
- Flink 中的窗口划分是 key 级别的
- 每条数据可能会属于多个窗口(取决于窗口定义的大小和滑动)
- 当 window 结束时间到达的时候,窗口一次性触发对应的输出
-
SESSION Window 会话窗口
- 这是一个动态merge的过程,一般会设置一个会话的最大的gap,如10min。
- Flink 中的窗口划分是 key 级别的
- 每条数据会单独划分为一个窗口,如果窗口之间有交集,则会对其进行merge。
- 当 window 结束时间到达的时候,窗口一次性触发对应的输出
- 例如某个 key 来第一条数据的时候,它的 window 就是
[event_time, event_time + gap],当这个 key 来另一条数据的时候,它会立即产生一个窗口,如果这个窗口跟之前的窗口有 overlap 的话,则会将两个窗口进行一个 merge,变成一个更大的窗口,此时需要将之前定义的 timer 取消,再注册一个新的 timer。
3.1.2 迟到数据处理
- 迟到数据的定义:
Watermark 驱动某个窗口触发输出之后,这个窗口如果后面又来了数据,那这种情况就属于是迟到的数据。(它所属的窗口已经被触发才算迟到)
- 产生迟到数据的情况
只有事件时间下才会有迟到的数据
- 迟到数据的数据方式
- 使用 Allow lateness 方式,需要设置一个允许迟到的时间,窗口正常计算结束后,不会马上清理状态,而是会多保留允许的时间,若有迟到数据,则继续之前的状态进行计算(适用于 DataStream 和 SQL )
- 使用 SideOutput 方式,需要对迟到数据打一个 tag,然后根据 tag 获取到迟到数据,把其转变成一个单独的流,再由用户自己来决定如何处理这部分数据(只有在 DataStream 窗口中才可以使用)
- 直接丢弃
3.1.3 增量计算 VS 全量计算
-
增量计算:
- 每条数据到来后,直接参与计算,window 只存储计算结果。
- 典型的 reduce、aggregate 等函数都是增量计算
- SQL 中主要是窗口聚合,所以都是可以增量计算的
-
全量计算:
- 每条数据到来后,先放到一个 buffer 中,会存储到状态里,直到窗口触发输出的时候,才会所有数据拿出来统一进行计算。
- 典型的 process 函数是全量计算
3.1.4 EMIT 触发
为了获得实时计算输出,使用 EMIT 机制,在 window 没有结束的时候,提前把 window 计算的部分结果输出出来。
在 DataStream 里通过自定义 Trigger 来实现,结果可以是:
- CONTINUE
- FIRE(触发计算,但是不清理)
- PURGE
- FIRE_AND_PURGE
在 SQL 里通过配置可以实现:
- table.exec.emit.early - fire.enabled = true
- table.exec.emit.early - fire.delay = {time}
3.2 Window 高级优化(只限于在 SQL 中的窗口)
3.2.1 Mini - batch 优化
- 主要解决的问题:只有一小批数据再进行计算,每个 key 的状态访问只有一次,这样在单个 key 的数据比较集中的情况下,对于状态访问可以有效的降低频率,最终提升性能。
- 主要适用环境:没有窗口的聚合场景。
3.2.2 Local - global 优化(分布式系统中典型的优化)
- 主要解决的问题:降低了数据 shuffle 的量,同时缓解数据的倾斜。
- 第一阶段先做一个 local 的聚合,直接跟在上游算子之后进行处理;第二阶段是对第一阶段的结果做一个 merge。
3.2.3 Distinct 状态复用
- 主要解决的问题:降低状态量
- 把相同字段的 Distinct 计算用一个 map 的 key 来存储,在 map 的 value 中,用一个 bit vector 来实现就可以把各个状态复用到一起。
3.2.4 Pane 优化(滑动窗口)
- 主要解决的问题:降低滑动窗口的状态存储量
- 将窗口状态划分成更小粒度的 pane,每来一条数据,只更新对应的 pane 的结果就可以了。当窗口需要输出结果的时候,只需要将这个窗口对应的 pane 的结果 merge 起来就可以了。
4. 案例分析
4.1 使用 Flink SQL 计算抖音的日活曲线
table.exec.emit.early - fire.enabled = truetable.exec.emit.early - fire.delay = 5mintable.exec.window.allow - retract - input = true
通过两阶段聚合来把数据打散,完成第一轮聚合,第二轮聚合只需要对各个的结果求和即可。
4.2 计算大数据任务的资源使用
- 问题描述:大数据任务(特指离线任务)运行时通常会有多个container启动并运行,每个container在运行结束的时候,YARN会负责将它的资源使用(CPU、内存)情况上报。一般大数据任务运行时间从几分钟到几小时不等。
- 需求:根据YARN上报的各个container的信息,在任务结束的时候,尽快的计算出一个任务运行所消耗的总的资源。假设前后两个container结束时间差不超过10min。
通过会话窗口来讲数据划分到一个 window 中,然后再将结果求和即可。