这是我参与「第四届青训营 」笔记创作活动的第 6 天!
1、流计算概述
(1)处理时间窗口
- 实时计算:处理时间窗口
- 数据实时流动、计算,窗口结束直接发送结果,不需要同期调度任务。
(2)处理时间 VS 事件时间
- 处理时间:数据在流式计算系统中真正处理时所在机器的当前时间
- 事件时间:数据产生的时间,比如客户端、传感器、后端代码等上报数据时的时间
(3)事件时间窗口
- 实时计算:事件时间窗口
- 数据实时进入到真实事件发生的窗口中进行计算,可以有效的处理数据延迟和乱序
- 窗口结束时间:没办法预知。故引出:Watermark
(4)Watermark
- 在数据中插入一些watermark,来表示当前的真实时间
- 在数据乱序的时候,watermrk 比较重要,它可以用来在乱序容忍和实时性之间做一个平衡
2、关于Watermark
(1)Watermark了解
- Watermark 表示当前真实的事件时间
- 如何产生:
SQL:
CREATE TABLE order(
user BIGINT,
product STRING,
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (. . .);
DataStream:
WatermarkStrategy
·<Tuple1<Long, String>>forBoundedOutOfOrderness(Durations(20))
·withTimestampAssigner((event, timestamp) -> event.f0);
- 如何传递:
- 如何通过 Flink UI 观察 Watermark:
(2)典型问题
-
Per-partition VS per-subtask watermark 生成
- Per-subtask watermark 生成
- 早期版本都是这种机制。如果一个 source subtask 消费多个 partition,那么多个partition 直接按的数据读取可能会加剧乱序程度。
- Per-partition watermark 生成
- 新版本引入了基于每个 partition 单独的 watermark 生成机制,这种机制可以有效避免上面的问题。
- Per-subtask watermark 生成
-
部分 partition/subtask 断流
- 根据上面提到的 watermark 传递机制,下游 subtask 会将上游所有 subtask 的 watermark 值的最小值作为自身的 watermark 值。如果上游有一个 subtask 的 watermark 的不更新了,则下游的 watermark 都不更新。
- 解决方案:Idle source
- 当某个 subtask 断流超过配置的 idle 超过时间时,将当前 subtask 设置为 idle,并下发一个 idle 的状态给下游。下游在计算自身 watermark 的时候,可以忽略掉当前是 idle 的那些 suatask。
-
迟到数据处理
- 因为 watermark 表示当前事件发生的真实时间,那晚于 watermark 的数据到来时,系统会认为这种数据是迟到的数据。
- 算子自身来决定如何处理迟到数据
- Window 聚合:默认丢弃
- 双流 JOIN:如果是 OUTER JOIN,则认为他不可能 JOIN 到任何数据
- CEP:默认丢弃
3、Window
(1)基本功能
-
Window 分类
- Tumble Window(滚动窗口)
- 窗口划分:每个 key 单独划分;每条数据只属于一个窗口
- 窗口触发:Window 结束时间到达的时候一次性触发
- Sliding Window(滑动窗口)
- 窗口划分:每个 key 单独划分;每条数据可能会属于多个窗口
- 窗口触发:Window 结束时间到达的时候一次性触发
- Session Window(会话窗口)
- 窗口划分:每个 key 单独划分;每条数据会单独地划分一个窗口如果 Window 之间有交集,则会对窗口进行 merge
- 窗口触发:Window 结束时间到达的时候一次性触发
- Tumble Window(滚动窗口)
-
数据迟到
- 定义:
- 产生情况:只有事件时间下才会有迟到的数据
- 默认处理:丢弃
- 迟到数据处理:
- Allow lateness:这种方式需要设置一个允许迟到的时间。设置之后,窗口正常计算结束后,不会马上清理状态,而是会多保留 allowLateness 一样长的时间;如果还有数据在这段时间到来,则继续之前的状态进行计算。(适用于:DataStream、SQL)
- SideOutput(侧输出流):这种方式需要对迟到数据打一个 tag,然后在DataStream 上根据这个 tag 获取到迟到数据流,然后业务层面自行选择进行处理。(适用于:DataStream)
- 定义:
-
增量计算
- 每条数据到来,直接进行计算,window 只存储计算结果。比如计算 sum,状态中只需要存储 sun 的结果,不需要保存每条数据。
- 典型的 reduce、aggregate 等函数都是增量计算。
-
全量计算
- 每条数据到来,会存储到 window 的 state 中,等到window 触发计算的时候,将所有数据拿出来一起计算。
- 典型的 process 等函数都是全量计算
-
EMIT 触发
- EMIT:在 Window 没有结束的时候,提前把 window 计算的部分输出出来。
- 实现:
- DataStream 里面通过自定义 Trigger 来实现,Trigger 的结果可以是:FIRE(触发计算,但是不清理)、CONTINUE、PURGE、FIRE_AND_PURGE...
- SQL 里面通过配置:table.exec.early-fire.enabled=true,或者 table.exec.early-fire.delay=(time)
(2)高级优化
-
mini-batch 优化
-
倾斜优化 —— local-global
-
Ddistinct 计算状态复用
-
Pane 优化
4、案例分析
- 问题:所以数据都需要在一个 subtask 上完成窗口计算,无法并行
- table.exec.early-fire.enabled = true
- table.exec.early-fire.delay = 5min
通过两阶段聚合来把数据打散,完成第一轮聚合,第二轮聚合只需要对各个分桶的结果求和即可。
- table.exec.window allow-retract - input = true