这是我参与「第四届青训营」笔记创作活动的第3天
一、概述
1.流式计算vs批式计算
| 特性 | 批式计算 | 流式计算 |
|---|---|---|
| 数据存储 | HDFS、Hive | Kafka、Pulsar |
| 数据时效性 | 天级别 | 分钟级别 |
| 准确性 | 精准 | 精准和时效性之间取得 |
| 典型计算引擎 | Hive、Spark、Flink | Flink |
| 计算模型 | Exactly-once | At Least Once/Exactly Once |
| 资源模型 | 定时调度 | 长期持有 |
| 主要场景 | 离线天级别数据报表 | 实时数仓、实时营销、实时风控 |
数据价值:实时性越高、数据价值越高
2.批处理
批处理模型典型的数仓为T+1架构,即数据计算是天级别的,当天只能看到前一天的计算结果。 通常使用的计算引擎为Hive或Spark等。计算的时候,数据是完全ready的,输入和输出都是确定性的。
划分为以小时单位调度,是否可以做到更实时的数仓?
1.批计算需要申请释放资源,有一个周期调度的过程,比较消耗资源
2.线上的数仓任务从几分钟到几小时不等,数仓的建模是分层的,整个的流程要在1个小时之内完成比较困难
如何更实时?
3.处理时间窗口
实时计算:处理时间窗口
数据是实时流动,实时计算,窗口结束直接发送结果,不需要周期调度任务
流式任务可以直接对数据划分成一个小时,一个小时的任务,窗口结束后,直接下发,不需要等小时结束后数据准备完了再计算,而是每来一条就计算。
4. 处理时间VS时间时间
通常处理时间有一些延迟。
- 处理时间:数据在流式计算系统中真正处理时所在机器的当前时间。
- 事件时间:数据产生的时间,比如客户端、传感器、后端代码等上报数据时的时间。
5.事件时间窗口
实时计算:事件时间窗口
数据实时进入到真实事件发生的窗口中进行计算,可以有效地处理数据延迟和乱序。
什么时候窗口才算结束?
有可能11点前的数据在11点半之后还会到
6.Watermark
在数据中插入一些watermark,来表示当前的真实时间,配合事件窗口处理数据乱序。
这个数据时间戳依次递增,属于比较完美的时间。11之后,有一个watermark(11),认为已经到了11时刻了,如果后面有比11小的数据到来,就认为时延迟数据,不应该参与前面的计算。
在数据存在乱序的时候,watermark就比较重要了,它可以用来在乱序容忍和实时性之间做一个平衡。
watermark(11)左边没有比他小的数据,如果有,说明是一个迟到的数据,前面的窗口已经计算过了,对他进行特殊处理,不影响之前的窗口计算。
二、watermark
1.什么是Watermark
表示系统认为的当前真实的事件时间
在flink里如何产生watermark?
Flink中有三大API,第一类是SQL,TABLE API,第二类是DtaStream
第一类SQL
倒数第二行生成watermark,从原始数据order_time减去5s作为watermark的数值。
第二类DataStream
用事件时间减去固定的20s作为watermark的数值
3.如何传递Watermark?
三个task,source,map,window。一个source可以消费两个MQ的分区partition,source的watermark是33,传递给下游,下游收到之前watermark是29,一对一传递是比较简单的。多对多传递,例如weindow1会取收到的最小值,作为watermark。
4.如何通过Flink UI观察watermark?
为什么开发了window有数据输入没有数据输出?看watermark是否是正常的。
source读出来会有一个sql生成的assigner的算子,传给下游window生成的算子,会有一个时间戳是否是正常的,如果没有时间戳,可能上游数据不够多,或者数据有异常。
5.典型问题一
Per-partition VS per-subtask watermark生成
Per-subtask watermark生成
早期版本都是这种机制。典型的问题是如果一个source suubtask消费多个partition,分区之间读取可能速度不同,那么多个partition之间的数据读取可能会加剧乱序程度。
Per-partition watermark生成
新版本引入了基于每个partition单独的watermark生成机制,这种机制可以有效避免上面的问题。
6.典型问题二
部分partition/subtask断流
根据上面提到的watermark传递机制,下游subtask会将上游所有的subtask的watermark值的最小值作为自身的watermark值。如果上游有一个subtask的watermark不更新了,则下游的watermark都不更新。
解决方案:Idle source
当某个subtask断流超过配置的idle超时时间时,将当前subtask置为idle,并下发一个idle的状态给下游。下游在计算自身watermark的时候,可以忽略掉当前是idle的那些subtask。
7.典型问题三
迟到数据处理
因为watermark表示点钱事件发生的真实事件,那晚于watermark的数据到来时,系统会认为这种数据时迟到的数据。
算子自身来决定如何处理迟到数据:
window聚合,默认会丢弃迟到数据
双流join,如果是outerjoin,则可以认为它不能join到任何数据
CEP,默认丢弃
三、Window
1.基本功能
(1)window分类与使用
典型的window:
- Tumble Window(滚动窗口)
- Sliding Window(滑动窗口)
- Session Winsow(会话窗口)
其它Window:
- 全局window
- count window
- 累计窗口
- ...
Flink 的API是分层的
层次越高,抽象程度越高,用户使用成本越低,表达能力更有限。
sql在聚合的地方加一个TUMBLE ,窗口大小时间
datastream做一个keyby,加一个窗口,5秒的处理时间,做一个sum
- 滚动窗口
三个用户,一共有14个窗口
窗口划分:
每个key单独划分
每条数据只会属于一个窗口
窗口触发:
Window结束时间到达的时候一次性触发
数据进来就开始计算,输出是等窗口结束输出
- 滑动窗口
窗口划分:
每个key单独划分
每条数据可能会属于多个窗口
窗口触发:
Window结束时间到达的时候一次性触发
- 会话窗口
数据到来,不能确定数据最终所属的窗口是哪个,动态划分,新数据到来可能和前窗口合并。
窗口划分:
每个key单独划分
每条数据会单独划分为一个窗口,如果window之间有交集,则会对窗口进行merge>
窗口触发:
Window结束时间到达的时候一次性触发
(2)迟到数据处理
怎么定义迟到?
一条数据到来后,会用WindowAssigner给它划分一个window,一般时间窗口是一个时间区间,比如[10:00,11:00),如果划分出来的window end比当前的watermark值还小,说明这个窗口已经出发了计算了,这条数据会被认为是迟到数据。
什么情况下会产生迟到数据?
只有事件时间下才会有迟到的数据,处理时间不会产生。
迟到数据默认处理?丢弃
- Allow lateness
这种方式需要设置一个允许迟到的时间。设置之后,窗口正常计算结束后,不会马上清理状态,而是会多保留allowLateness这么长时间,在这段时间内如果还有数据到来,则继续之前的状态进行计算。会应用到retract机制。
适用于:DataStream、SQL
2.SideOutput(侧输出流) 这种方式需要对迟到数据打一个tag,然后再DataStream上根据这个tag获取迟到数据,然后业务层面自行选择进行处理。
适用于:DataStream
增量计算:
- 每条数据到来,直接进行计算,window只存储计算结果。比如计算sum,状态中只需要存储sum的结果,不需要保存每条数据。
- 典型的reduce、aggregate等级函数都是增量计算 -SQL的聚合只有增量计算
全量计算:
- 每条数据到来,会存储到window的state中。等到window出发计算的时候,将所有数据拿出来一起计算。
- 典型的process函数就是全量计算。
(3)EMIT触发
什么叫EMIT? 通常来讲,winsow都是在结束的时候才能输出结果,比如1h的tumble window,只有在1个小时结束的时候才能统一输出结果。
如果窗口比较大,把比如1h或者1天,甚至于更大的话,那计算结果输出的延迟就比较高,失去了实时计算的意义。
EMIT输出指的是,在window没有结束的时候,提前把window计算的部分结果输出出来,可以中间结果输出多次,下游可以看到不断变换的结果,也需要用到retract机制,最终满足一致性语义。
怎么实现? 在DataStream里可以通过自定义Trigger来实现,Trigger的结果可以是:
- CONTIUNE
- FIRE(出发计算,但是不清理)
- PURGE
- FIRE_AND_PURGE
SQL也可以使用,通过配置:
- table.exec.emit.early-fire.enabled=true
- table.exec.emit.early-fire.delay=(time)
2.Window-高级优化
1. Mini-batch优化解决频繁访问的问题
中间输出每条结果,输出量大。user,count值状态需要保存,放到内存里或放到外部CB,大的状态一般会放到外部,内存会比较可控。放到外部会有序列化和反序列化,每条数据来了之后都要读取之前的状态,计算,写入,输出,对CPU开销比较大。
Mini-batch解决上述两个问题。积攒一小批数据,读一次状态,处理这一批数据,输出,写回状态。
但另外一个问题,这个机制的拓扑可能比较复杂,延迟可能比较高,真实的场景需要用到checkpoint传递机制和watermark协作,在上游添加一个assigner的算子,统一发起指令,开始一个新的mini-batch信号,下游收到信号后,就把之前的数据计算输出写状态,再去传递这个信号,在一个上游的信号周期内,完成整个链路的mini-batch的周期计算。
2.倾斜优化-local-global
数据倾斜定义:
是指在大规模并行处理的数据中,其中某个运行节点处理的数据远远超过其他部分,这会导致该节点压力极大,最终出现运行失败从而导致整个任务的失败。
产生数据倾斜的原因主要有 2 个方面:
业务上有严重的数据热点
技术上大量使用了 KeyBy、GroupBy 等操作,错误的使用了分组 Key,人为产生数据热点。
3.Dinstinct计算状态复用降低状态量
它是一个map,key是到达的数据,value是固定值表示来或没来过,状态复用量是比较客观的,离线的话服用程度是非常高的。
4.Pane优化降低滑动窗口的存储量
既属于window1,也属于window2,一天的窗口1小时滑动,每条数据参与的计算量是比较大的,窗口的开销也是比较大的。
不在数据到来把最终结果计算出来,三小时的窗口1小时的滑动,划分成更小的力度key,最终组合成窗口,每条数据来了后就像普通的滚动窗口,每条数据只属于一个window,计算量和存储量比较可控,最终需要再窗口输出结果的时候做一个merge。
四、案例
1.使用Flink SQL计算抖音的日活曲线
- table.exec.emit.early-fire.enabled=true
- table.exec.emit.early-fire.delay=(time)
做一个滚动窗口1天,计算count uid,得到一天内的用户的dau结果,用EMIT机制,能够实时输出,得到日货曲线输出。
问题: group by只有时间窗口,最终计算需要有高并发的全局窗口计算才能够聚合到所有用户的uid。所有数据都需要在一个subtask中完成窗口计算,无法并行。
通过子查询根据用户分桶,比如分成一万个桶,计算每个桶里的uid,通过二次聚合,但并发计算,把一万个桶全局聚合,取sum值得到最终结果。
- table.exec.emit.early-fire.enabled=true
- table.exec.emit.early-fire.delay=(time)
- table.exec.emit.allow-retract-input=true 通过两阶段聚合来把数据来把数据打散,完成第一轮聚合,第二轮聚合只需要对各个分桶的结果求和即可。(倾斜优化)
2.使用Flink SQL计算大数据任务资源使用
问题描述:大数据任务(特指离线任务),需要定期调度,运行时通常会有多个container启动并运行,每个container在运行结束的时候,YARN会负责将他的资源使用(CPU、内存)情况上报。一般大数据任务运行时间从几分钟到几小时不等。
需求: 根据YARN上报的各个container的信息,在任务结束的时候,尽快的计算出一个任务运行所消耗的总的资源。 假设前后两个container结束时间差不超过10min
应用id做一个groupby,写一个会话窗口gap设置成10分钟,对会话窗口内的所有container进行求和,就可以得到场景的最终结果。
五、参考文章
剖析Flink出现数据倾斜和解决办法_Platina_Tomato的博客-CSDN博客_flink数据倾斜
Flink优化04---数据倾斜_Johnson8702的博客-CSDN博客_flink如何解决数据倾斜