这是我参与「第四届青训营 」笔记创作活动的第二天
Flink流式计算
Flink是目前最主流的流式计算引擎,下面我们主要如下几个部分来对Flink进行介绍:Flink概述、Flink架构、Flink流批一体化、Flink容错机制、Flink窗口函数实现。
Flink概述
- Apache Flink诞生背景
- 大数据诞生:无法在一定时间内使用常规软件工具对其进行获取、存储、管理和处理的数据集合。大数据具有4V特点:Volume(海量化)、Velocity(快速化)、Variety(多样性)、Value(价值化)——大数据是海量快速增长且价值密度低的多样化数据
- 大数据技术进程:大数据经历史前阶段-离线计算-批处理-流式计算几个阶段,最终进入流式计算时代
- 为什么需要流式计算?大数据具有低密度价值的特点,数据量很大,价值也很大,但是价值密度很低,而大数据实时性带来的价值更大,比如说:监控场景、金融分控、实时推荐等。
- Apache Flink优势
- 流式计算引擎对比 目前Flink在一致性保证,延迟,吞吐,容错,支持等方面都有一定优势。
- Flink技术特点:Flink具有窗口等高阶需求支持,流批一体而且很不错的容错机制(状态容错,Exactly-Once)等特点
- Apache Flink开源生态圈 Flink目前支持大部分关系型数据库、消息中间件、大数据存储作为数据源,内部提高SQL、DataStream、pyFlink等方式来进行计算开发。同时Flink还支持主流的k8s进行资源管理,上层还是支持FlinkML等进行机器学习相关计算。
Flink架构
- Flink分层架构 从下到上Flink主要分层资源调度层、状态存储层、执行引擎(Runtime)层以及SDK层
- 资源调度层:Flink可以支持部署多种环境(Standalone、K8s、Yarn)
- 状态存储层:负责存储算子的状态信息
- 执行引擎层:执行引擎层提供统一的DAG API,用于描述数据处理的Pipeline,无论是流还是批,都会转化为DAG图,调用层再将DAG转换为分布式环境下的Task,Task之间通过Shfulle传输数据
- SDK层:Flink的SDK目前只支持三类,SQL/Table(表数据)、DataStream、Python(pyflink)
- Flink整体架构
- 整体架构:一个Flink集群,主要包含一下两个核心组件:JobManager(JM),负责整个任务的协调工作包括:调度task、触发协调Task做Checkpoint、协调容错恢复等
- JobManager:协调 Flink 应用程序的分布式执行。它决定何时调度下一个 task(或一组 task)、对完成的 task 或执行失败做出反应、协调 checkpoint、并且协调从失败中恢复等等。这个进程由三个不同的组件组成:Dispatcher:提供了一个 REST 接口,用来提交 Flink 应用程序执行,并为每个提交的作业启动一个新的 JobMaster。它还运行 Flink WebUI 用来提供作业执行信息。JobMaster:负责管理单个JobGraph的执行,管理一个job的整个生命周期,会向ResourceManager申请slot,并调度task任务到对应的TM上。Flink 集群中可以同时运行多个作业,每个作业都有自己的 JobMaster。ResourceManager: 负责 Flink 集群中的资源提供、回收、分配 Task manager拉起之后会向RM注册。 它管理 task、slots,这是 Flink 集群中资源调度的单位。Flink 为不同的环境和资源提供者(例如YARN、Kubernetes 和 standalone 部署)实现了对应的 ResourceManager。
- TaskManager:执行作业流的 task,并且缓存和交换数据流。每一TaskManager都是JVM进程,并且可以在独立的线程中执行一个或者多个任务。要控制接受任务数量,可以调节slot(至少一个)。在TaskManager 中资源调度的最小单位是 task slot。TaskManager 中 task slot 的数量表示并发处理 task 的数量。对于分布式执行,Flink 将算子的 subtasks 链接成 tasks,每个task由一个线程执行。将算子链接成 task 是个有用的优化:它减少线程间切换、缓冲的开销,并且减少延迟的同时增加整体吞吐量。分配资源意味着subtask不会与其他作业的subtask竞争托管内存,注意此处没有 CPU 隔离。
- Flink架构优化
- 流/批/OLAP支持
三个业务场景特点
| 流式计算 | 批式计算 | 交互式分析 |
|---|---|---|
| 实时计算 | 离线计算 | OLAP |
| 延迟在秒级以内 | 处理时间为分钟到小时级别甚至天级别 | 处理时间秒级 |
| 0 ~ 1s | 10s ~ 1h+ | 1 ~ 10s |
| 广告推荐、金融分控 | 推荐引擎构建索引、批式数据分析 | 数据分析BI报表 |
三种业务场景的解决方案挑战
| 模块 | 流式计算 | 批式计算 | 交互式分析(OLAP) |
|---|---|---|---|
| SQL | Yes | Yes | Yes |
| 实时性 | 高、处理延迟毫秒级别 | 低 | 高、查询延迟在秒级,且要求高并发查询 |
| 容错能力 | 高 | 中,大作业失败重跑代价高 | No,失败重跑即可 |
| 状态 | 高 | No | No |
| 准确性 | Exactly Once,要求高,重跑需要恢复状态 | Exactly Once,失败重跑即可 | Exactly Once,失败重跑即可 |
| 扩展性 | Yes | Yes | Yes |
三种业务场景可以用一套引擎解决吗?
批式计算是流式计算的特例,Everything is Streams,有界数据集(批式数据)也是一种数据流、特殊的数据流;OLAP计算是一种特殊的批式计算,它的并发和实时性要求比较高,其他情况比普通批式作业没有特别大区别。
待解决问题
| Btach场景需求 | OLAP场景需求 |
|---|---|
| 流批一体支持 | 短查询作业场景 |
| 1. Unify DataStream API | 1. 高并发支持 |
| 2. Scheduler | 2. 极致处理性能 |
| 3. Shuffle Service | |
| 4. Failover Recovery |
Apache Flink 最终架构设想:
- Flink作业示例
- WordCount示例,从kafka中读取一个实时数据流,没10s统计一次单词出现次数,实现代码如下:
- 将业务逻辑转换为一个Streaming DataFlow Graph
- 假设作业的sink算子的并发配置为1,其他算子并发为2将上面的Streaming DataFlow Graph转为Parallel Dataflow(内部叫Execution Graph)
-
为了更有效地分布式执行,Flink会尽可能地将不同地operator链接(chain)在一起形成SubTask。每个SubTask可以在一个线程中执行,内部叫做Operator Chain,如下图的source和map算子可以chain在一起
-
最后将上面的Task调度到具体的TaskManager中的slot执行,一个Slot只能运行同一个task的SubTask
Flink流批一体化
- 为什么要流批一体化
- 业务需求:比如说我们业务需要实时统计一个短视频的播放量、点赞数,实时观看人数等;同时还需要按天统计创作者的一些数据信息
- 架构痛点:人工成本比较高:批、流两套系统,相同逻辑需要开发两遍;数据链路冗余:本身计算内容是一致的,由于是两套链路,相同的逻辑需要运行两遍,造成一定的资源浪费;数据口径不一致:两套系统,两套算子,两套UDF,通常会产生不同程度的误差,这些误差会给业务带来非常大的困扰;
- 流批一体的挑战
- 流批业务场景特点不同
| 流式计算 | 批式计算 |
|---|---|
| 实时计算 | 离线计算 |
| 延迟在秒级以内 | 处理时间为分钟到小时级别,甚至天级别 |
| 0 ~ 1s | 10s ~ 1h+ |
| 广告推荐、金融分控 | 搜索引擎构建索引、批式数据分析 |
- 批式计算与流式计算核心区别
| 维度 | 流式计算 | 批式计算 |
|---|---|---|
| 数据流 | 无限数据流 | 有限数据流 |
| 时延 | 低延迟、业务会感知运行中的情况 | 实时性要求不高,会关注最终结果产出时间 |
- Flink如何做到流批一体
- 批式计算是流式计算的特例,Everything is Streams,有界数据流(批式数据)也是一种数据流、一种特殊的数据流;理论上使用一套引擎架构来解决流式计算和批式计算,只是对不同场景支持相应扩展性,并允许做不同的优化策略。
- Flink在架构设计上针对SQL层、DataStream API层、调度(Scheduler)层、Shuffle Service均完成对流和批的支持。
- Flink模块对流批一体的支持
- DataStream API层统一化,流批都可以使用DataStream API来开发;
- Scheduler调度层架构统一,支持流批场景; Flink1.2之前支持一下两种调度模式: | 模式 | 特点 |场景| | --- | --- | --- | | EAGER | 申请一个作业所有资源,然后调度所有Task,所有Task之间采用Pipeline的方法进行通信 |Stream作业场景| | LAZY | 先调度上游,等待上游产生数据或结束后在调度下游,类似Spark的stage执行模式 | Batch作业场景 |
Pipeline Region 调度
Pipleline的数据交换方式连接的Task构成一个Pipeline Regin;不管是流作业还是批作业,都是按照Pipleline Region粒度来申请资源和调度任务。(ALL_EDGES_BLOCKING,ALL_EDGES_PIPELINED)
- Shuffle Service层对流批一体支持 Shuffle概念:在分布式计算中,用于连接上下游数据交互(衔接)的过程
Shuffle实现:基于文件的Pull Based Shuffle,比如Spark或MR,它的特点是具有较高的容错性,适合较大规模的批处理作业,由于是基于文件的,他的容错性和稳定性会更好一些;基于Pipeline的Push Based Shuffle,比如Flink、Storm、Presto等,它的特点是低延迟和高性能,但是由于因为shuffle数据没有存储下来,如果是batch任务的化,就需要进行重跑恢复;
流和批Shuffle之间的差异:Shuffle数据的生命周期:流作业的Shuffle数据与Task是绑定的,而批作业的Shuffle数据与Task是解耦的;Shuffle数据存储介质:流作业生命周期比较短,而且流作业为了实时性,Shuffle通常存储在内存中,批作业因为数据量比较大以及容错的需求,一般会存储在磁盘里;Shuffle的部署方式:流作业Shuffle服务和计算节点部署在一起,可以减少网络开销,从而减少latency,而批作业不同;
Flink流批一体实现:在Streaming和OLAP场景,为了性能需要,通常使用基于Pipeline的Shuffle(一个pipeline region);在Batch场景:一般使用Blocking的Shuffle模式;Flink为了统一在Streaming和Batch模式下的Shuffle架构,Flink实现了一个Puggable的Shuffle Service框架,抽象出一些公共模块;Shuffle Service Flink开源社区的支持——Netty Shuffle Service,Remote Shuffle Service(pipeline模式走remote性能反而下降)
Flink容错机制
- 数据流与动态表
- 传统SQL与流处理
| 特征 | SQL | 流处理 |
|---|---|---|
| 处理数据的有界性 | 处理的表是有界的 | 流是一个无界元组序列 |
| 处理数据的完整性 | 执行查询可以访问完整的数据 | 执行查询无法访问所有的数据 |
| 执行时间 | 批处理查询产生固定大小结果后终止 | 查询不断更新结果,永不终止 |
- 数据流与动态表的转换
流——>表:动态表与表示批处理数据的静态表不同,动态表是随着时间改变的;
动态表->动态表:查询永不终止,查询结果会不断更新,产生新的动态表。需要存储每一个用户的URL计数(状态),以便增加该计数并在输入表接收新行时发送新结果。
- 总结:数据流和动态表之间转化,在数据流的查询不会终止,查询可能会有状态,用来不断更新查询的结果。查询结果出现故障怎么办?Flink容错机制
- Flink容错机制-语义
- 不同数据处理保证的语义
At-most-once:出现故障的时候,啥也不做。数据处理不保证任何语义,处理时延低;
At-least-once:保证每条数据均至少被处理一次,一条数据可能存在重复消费;
Exactly-once:最严格的处理语义,从输出结果来看,每条数据均被消费且仅被消费一次,仿佛故障从而发生。
- Flink容错机制-checkpoint(快照)
- 状态快照
JobManager负责统一制作快照(Checkpoint);状态恢复的时间点:等待所有处理逻辑消费完成source保留状态及之前的数据;简单快照制作算法:暂停处理输入数据,等待后续所有处理算子消费当前输入数据,作业所有算子复制自己的状态并保存到远端可靠存储。
- 状态恢复
从Storage中取出Checkpoint进行状态恢复
- Chandy-Lamport算法(异步快照)
背景:2个Source,2个sink,一个做奇数加法,一个做偶数加法。
快照制作开始:每个Source算子都收到JobManager发送的Checkpoint Barrier标识状态快照制作开始。
Source算子处理:各个source保存自己状态后,向所有连接的下游继续发送Checkpoint Barrier,同时告知JM自己状态已经制作完成。
Barrier Alignment:算子会等待所有上游的barrier到达后猜开始快照制作;已经制作完成上游算子会继续处理数据,并不会被下游算子制作快照工程堵塞。(ps:上游算子会向所有连接的下游算子传递barrier,下游算子收到所有连接的上游barrier开始制作快照,如有部分上游算子速度快,提前到达的处理数据进行缓存,不进行计算。)
checkpoint结束:所有算子都告知JM状态制作完成后,整个Checkpoint结束。
- 总结
解耦快照和数据处理过程,各个算子制作完成状态快照后就正常处理数据,不用等下游算子制作快照完成;在快照制作和Barrier Alignment过程中需要暂停处理数据,仍然会增加数据处理延迟;快照保存到远端可能极为耗时;
- Flink容错机制-两阶段提交(端到端的Exactly-once)
- 端到端的Exactly-once语义
checkpoint能保证每条数据都对各个有状态的算子更新一次,sink输出算子仍然可能下发重复的数据;严格意义的端到端的Exactly-once语义需要特殊的sink算子实现;
- 两阶段提交协议
在多个节点参与执行的分布式系统中,为了协调每个节点都能同时执行或者回滚事务性的操作,引入一个中心节点统一处理所有节点的执行逻辑,中心节点叫做协作者,被中心节点调度的其他业务节点叫做参与者;
预提交阶段:协作者向所有参与者发送一个commit信息;每个参与的协作者收到信息后,执行事务,但是不真正提交;若事务成功执行完成,发送一个成功的消息(vote yes),执行失败,则发送一个失败的消息(vote no);
提交阶段-正常:(协作者成功接收到所有参与者vote yes消息)协作者向所有参与者发送一个commit信息;每个收到commit消息的参与者释放执行事务所需的资源,并结束这次事务的执行;完成前一步操作,参与者发送一个ack消息给协作者;协作者收到所有参与者的ack信息后,标识该事务执行完成。
提交阶段-异常回滚:(协作者又收到参与者vote no的消息,或者发生等待超时)协作者向所有参与者发送一个rollback信息;每个收到rollback消息的参与者回滚事务的执行操作,并释放事务所占资源;完成前一步,参与者发送ack信息给协作者;协作者收到所有参与者的ack信息后,标识该事务成功完成回滚;
-
Flink中的2PC sink Job Manaager作为协作者,各个算子作为参与者
开始制作快照
制作快照的过程中就开启预提交commit
快照制作完成和提交是同步的
-
Flink两阶段提交总结
事务开启:在sink task向下游写数据之前,均开启一个事务,后续所有写数据的操作均在这个事务中执行,事务未提交前,事务写入的数据下游不可读。
预提交阶段:JobManager开始下发checkpoint Barrier,当各个处理逻辑接收到barrier停止处理后续数据,对当前状态制作快照,此时sink也不再当前事务下继续处理数据(为后续的数据需要新打开一个事务)。状态制作成功则向JM成功的消息,失败则发送失败的信息。(基本常驻的,每次barrier的数据开启一个事务)
提交阶段:若JM收到所有预提交成功的信息,则向所有处理逻辑(包括sink)发送可以提交此次事务的信息,sink接收到此信息后,则完成此次事务的提交,此时下游可以读到此次事务写入的数据;若JM有收到预提交失败的信息,则通知所有处理逻辑回滚这次事务的操作,此时sink则放弃这次事务提交的数据下。
流式计算Window计算-以Flink为例
- 基础知识点
-
处理时间 VS 事件时间 处理时间:数据在流式计算系统中真正处理所在机器的当前时间;事件时间:数据产生的时间,如客户端、传感器、后端代码等上报数据时的时间
-
事件时间窗口 实时计算:事件时间窗口;数据实时进入到真实时间发生的窗口进行计算,可以有效的处理数据延迟和乱序。(何时关闭窗口?)
-
Watermark 在数据中插入watermark,来表示当前的真实时间,它可以用来在乱序容错和实时性之间做一个平衡。
- Watermark
- 定义
表示系统认为的当前真实的事件时间
Watermark =进入Flink的最大事件时间(mxtEventTime)-指定的延迟时间(t)
如果有窗口的停止时间等于或者小于maxEvntTime-t(当时的watermark)窗口被触发执行
- watermark场景
有序Stream的watermark:如果数据元素的事件事件是有序的,watermark时间戳会随着数据元素事件按时间顺序生成,所以watermark=maxtime-0=maxtime
乱序事件中的Watermark:现实情况下数据元素往往并不是按照其产生顺序接入到Flink 系统中进行处理,而频繁出现乱序或迟到的情况,则需要使用 Watermarks 。比如下图,设置延迟时间t为2
并行数据流中的Watermark:再多并行度的情况下,Watermark会有一个对齐机制,这个对齐机制会取所有channel中最小的matermark
- 使用 SQL
CREATE TABLE Orders (
user BIGINT,
product STRING,
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL '5'SECOND
) WITH ( ...);
DataStream
WatermarkStrategy
.<Tuple2<Long, String>>forBoundedOutOfOrderness(Duration.ofSeconds(20))
.withTimestampAssigner((event, timestamp)->event.f0);
- Watermark传递
Per-partition watermark:基于每个partition单独的watermark生成机制,可以避免多个partition之间数据读取加剧乱序程度;partition/subtask断流,上游subtask的watermark不更新则下游的watermark都不更新(subtask断流超时);迟到数据处理:因为watermark表示当前事件发生的真实时间,那晚于watermark的数据到来时,系统会认为数据是迟到的数据。
- Window窗口
-
基础 分类:Tumble Window(滚动窗口)、Sliding Window(滑动窗口)、Session Window(会话窗口)、全局窗口、Count Window、累计窗口...等等
-
使用 SQL API:
SELECT
user,
TUMBLE_START(order_time, INTERVAL '1' DAY) AS wStart,
SUM(amount) From Orders
GROUP BY
TUMBLE(order_time, INTERVAL '1' DAY),
user
DataStream API:
DataStream<Tuple2<String, Integer> dataStream = env
.socketTextStream("loclhost", 9999)
.flatMap(new Solitter())
.keyBy(value -> value.f0)
.window(TumblingProcessingTimeWindows.of(Time.second(5)))
.sum(1);
-
滚动窗口 窗口划分:每个key单独划分,每条数据只会属于一个窗口 窗口触发:Window结束事件到达的时候一次性触发
-
滑动窗口 窗口划分:每个key单独划分,每条数据可能属于多个窗口 窗口触发:Window结束时间到达时候一次性触发
-
会话窗口 窗口划分:每个key单独划分,每条数据会单独划分为一个窗口,如果window之间有交集则会对窗口进行merge
-
迟到数据处理
定义:一条数据到来后,会用WindowAssigner给它划分一个window,一般时间窗口就是一个时间区间如[10:00 11:00),如果划分出来的window end 比当前的watermark值还小,说明窗口已经计算,这条数据认为是迟到数据。只有事件事件会有迟到数据,迟到数据默认处理是丢弃。
Allow lateness:设置一个允许迟到的事件,设置以后窗口正常计算结束后,不会马上清除状态,而是多保留allowlateness时间,如果在这段时间内有数据到来,则继续之前的状态进行计算。适用于:DataStream、SQL
SideOutput(侧输出流):对迟到的数据打一个标签(tag),然后在DataStream上根据tag获取到迟到数据流,然后业务层面自行选择处理。适用于:DataStream
- 增量VS全量计算 增强计算:每条数据到来直接进行计算,window只存储计算结果sum,reudce,aggregate都是增量计算。SQL中的聚合只有增量计算
全量计算:每条数据到来会存储在window的state中。等到window触发计算的时候,将所有数据拿出来一起计算。process函数就是全量计算
- EMIT触发 EMIT输出指的是,在window没有结束的时候,提前把window计算的部分结果输出出来。实现方式:在DataStream里面可以通过自定义Trigger来实现,CONTINUE,FIRE(触发计算,但是不清除),PURGE,FIRE_AND_PURGE
- 窗口高级优化
- Mini Batch优化
- 倾斜优化- local-gobal
类似于预聚合
- DISTINCT计算状态复用
- Panel优化