这是我参与「第四届青训营 」笔记创作活动的第3天
1. 实时计算和批式计算的本质区别
2. 实时计算中的核心功能:watermark机制、window机制
3. 了解3大基本窗口类型的定义、使用以及核心原理
4. 窗口机制中的最核心的优化和原理
批处理模型典型的数仓架构为T+1架构(即当天只能看到前一天的计算结果)。
通常的计算引擎为Hive和Spark,数据计算以天级别。
小时级批计算面临的问题:除了计算之外,申请、调度也消耗很资源;数仓计算的时间业务、建模是分层的,很多情况下是做不到短时间内的数据的产生和完成处理。
实时计算:处理时间窗口。数据实时流动,实时计算,窗口结束直接发送结果,不需要周期调度任务。
实时计算分成:处理时间和事件时间。
处理时间:数据在流式计算系统中真正处理时所在机器的当前时间。
事件时间:数据产生的时间,比如客户端、传感器、后端代码等上报数据时的时间。
事件时间和处理时间之间在实际情况下存在延迟。
实时计算:事件时间窗口
数据实时进入到真实事件发生的窗口中计算,可以有效的处理数据延迟和乱序。
问题:什么时候窗口才算结束?
Watermark
Watermark:表示系统认为的当前真实的事件时间。Watermark通过watermark generator来生成。
在数据中插入一些watermark,用于表示当前的时间。
Watermark的功能讲例:
1. watermark设为11,若后来来的数据的时间值小于11,则认为这个数据为延迟数据,不将其加入到前面的数据处理中。不影响之前的窗口
2. 用于在乱序容忍和实时性做平衡。(watermark间隔短则乱序容忍小,实时性高)
Watermark传递(和checkpoint有相似之处,可互相参照比对)
多source的watermark传递,下游接收到多个watermark的话,取其中的最小值作为当前算子的watermark,再往下传递。
典型问题:为什么自己开发的flink窗口的时间窗口的程序,有数据输入但没有数据输出?
需要怀疑watermark是否正常。
通过Flink UI观察Watermark
根据时间戳看是否正常,上游数据不够多或异常,或者数据有但watermark不太对,window里的数值比较小、还没到触发窗口的输出时间。
典型问题一:
生成watermark的方式的不同
- per-subtask watermark生成:
若一个source subtask消费多个partition,那么多个partition之间的数据读取可能会加剧乱序。
- Per-partition watermark生成:
给予每个partition单独的watermark生成机制,可避免上面的问题。
典型问题二:
- 部分partition/subtask断流:如果上游有一个subtask的watermark不更新了,则下游的watermark都不更新了。(因为下游要取上游的最小值)
解决方案:Idle source
当某个subtask(算子)断流超过配置的idle超时时间时,将当前subtask置为idle,并下发一个idle状态给下游。下游在计算自身watermark的时候,可以忽略掉当前是idle的subtask。
典型问题三:
- 迟到数据处理:因为watermark表示当前事件发生的真实时间,晚于watermark来的数据系统认为是迟到数据。
算子自身来决定如何处理迟到数据:
-window聚合,window算子会默认丢弃迟到数据
-双流join,如果是outer join,join算子则认为此数据不能join到任何数据
-CEP(复杂事件处理),默认丢弃
Window
Window分类
1. tumble window(滚动窗口)
2. Sliding window(滑动窗口)
3. Session window(会话窗口)
另外还有:全局window、count window、累计窗口。。。。
Window使用
此图内:层次越高,抽象程度越高,用户使用成本越低。
滚动窗口
窗口划分:
1. 每个key单独划分(例图中的user1、user2、user3)
2. 每条数据只会属于一个窗口
窗口触发:Window结束时间到达的时候一次性触发
滑动窗口
窗口划分:
1.每个key单独划分(例图中的user1、user2、user3)
2.每条数据可能会属于多一个窗口
窗口触发:Window结束时间到达的时候一次性触发
会话窗口
窗口划分:
1.每个key单独划分(例图中的user1、user2、user3)
2.每条数据会单独划分一个窗口,如果window之间有交集,则会对窗口进行merge
窗口触发:Window结束时间到达的时候一次性触发
以时间划分窗口(比如十分钟的窗口,但是内部数据可能来得快也可能来的慢,窗口内的数据尺寸不固定)
窗口会合并(十分钟的窗口,0-10min一个窗口,在5min(5-15)时来了个数据,则窗口扩展为0-15min)
迟到数据处理
怎么定义迟到?
一条数据到来后,会用windowassigner划分一个window,一般时间窗口是一个时间区间,比如[10:00,11:00),如果划分出来的window end比当前的watermark还小,说明这个窗口的已经触发了计算,会被认为是迟到数据。(宽松一点的话,窗口不触发计算,则可以认为没迟到,后续可进行处理)
只有事件时间才会有迟到的数据。处理时间不会又迟到时间。
迟到数据默认丢弃。
迟到数据处理方式
1. Allow laterness
设置一个允许迟到的时间,设置之后,窗口正常计算结束后,不会马上清理状态,而是会多保留allowlateness这么长时间,在这段时间内如果还有数据到来,则继续之前的状态进行计算。
适用于Datastream、SQL
这是一种“修正”的方法,含有retract的机制。
2. Sideoutput(侧输出流)
对迟到数据打一个tag,然后在Datastream上根据这个tag获取到迟到数据流,然后业务层面自行选择进行处理。
适用于Datastream
增量和全量计算(窗口计算模型)
增量计算:
-每条数据到来,直接进行计算,window只存储计算结果,比如计算sum,状态中只需要sum的结果,不需要保存每条数据
-典型的reduce,aggregate等函数都是增量计算
-SQL中的聚合只有增量计算
全量计算
-每条数据到来,先存储到window的state中,等到window触发计算的时候,将所有数据那出来一起计算。
-process函数就是全量计算
EMIT触发机制
通常,window都是在结束的时候才能输出结果,比如1h的tumble window,只有在一个小时结束的时候才能统一输出结果。
如果窗口比较大,比如1h或者1天,甚至于更大的话,那计算结果输出的延迟就比较高,失去了实时计算的意义。
EMIT输出指的是,在window没有结束的时候提前把window计算的部分结果输出出来。
实现:
在datastream里面可以通过自定义trigger来实现,trigger的结果可以是:
-CONTINUE
-FIRE(触发计算,但是不清理窗口状态)
-PURGE
-FIRE_AND_PURGE
SQL也可以使用,通过配置:
-table.exe.emit.early-fire.enabled=ture
-table.exe.emit.early-fire.delay=(time)
高级优化
Mini-batch优化
解决的问题:1.中间结果偏多 2.状态访问比较频繁
思路:算子攒一小批数据,读一次状态,处理这小批数据,然后输出,并写回状态。
缺点:如果任务的拓扑比较复杂,每个算子都要攒一批数据再处理,其中的延迟会在流程和网络中不断累计,形成巨大延迟。
可以使用checkpoint和watermark机制协调上述问题,统一发出一个指令:开始当前新的一个mini-batch的信号,下游每个算子收到信号后把之前的数据计算输出、写状态,并下传。相当于在一个上游的信号周期内完成整个链路里的算子mini-batch的工作。
倾斜优化-local-global
工作之前需要对数据进行预聚合、预处理,中间结果发到最终的地方去处理。
Distinct计算状态复用
Pane优化
例:一个小时的窗口,一分钟的滑动,滑动/窗口size太小,这样要参与窗口的数据量是巨大的, 计算开销也巨大。
划分成小粒度,不是窗口,但可以组成窗口。
1. mini-batch优化解决频繁访问状态的问题
2. Local-global优化解决倾斜问题
3. Distinct状态复用降低状态量