文章目录
- 1. Flink 概述
- 1.1. Flink 特点
- 1.2. Flink 和 Spark Streaming 对比
- 2. Filnk 运行架构
- 2.1. Yarn 任务运行流程
- 2.2. Flink 线上部署
- 2.3. Flink 运行组件
- 2.3.1. Flink Client 客户端
- 2.3.2. JobManager 作业管理器
- 2.3.3. ResourceManager 资源管理器
- 2.3.4. TaskManager 任务管理器
- 2.3.5. Dispatcher 分发器
- 3. Flink API
- 3.1. Transform 算子
- 3.2. Window 窗口
- 3.3. 时间语义
- 3.4. Watermark
- 3.4.1. Watermark 产生背景
- 3.4.2. Watermark 工作机制
- 3.4.3. Watermark 任务间传递
- 3.4.4. 延迟数据补充方案
- 3.5. 状态编程
- 3.5.1. 状态类型
- 算子状态(operator state)
- 键控状态(keyed state)
- 3.5.2. 状态一致性
- 3.5.3. Flink 内部一致性
- 3.5.4. end to end 端到端一致性
- 3.6. 容错机制
- 3.6.1. checkpoint 一致性检查点
- 3.6.2. savepoint 保存点
- 4. Flink 知识点
- 4.1. Flink 如何实现批流一体
- 4.2. Flink 内存管理与对象[反]序列化
- 4.3. Flink延迟高调优因素
- 4.4. Flink 数据交换机制
- 4.5. Flink 反压
- 4.6. Flink SQL 的实现
1. Flink 概述
Apache Flink 是用于对无界和有界数据流进行有状态计算,分布式、高性能、随时可用以及准确的开源流处理框架和分布式处理引擎。
1.1. Flink 特点
- 事件驱动型 (Event-driven)
事件驱动型应用是一类具有状态
的应用,它从一个或多个事件流提取数据,并根据到来的事件触发计算、状态更新或其他外部动作。比较典型的就是以 kafka 为代表的消息队列几乎都是事件驱动型应用。 - 流处理架构
流处理的特点是无界、实时
, 无需针对整个数据集执行操作,而是对通过系统传输的每个数据项执行操作,一般用于实时统计。
在 flink 的世界观中,一切都是由流组成的,离线数据是有界限的流,实时数据是一个没有界限的流,这就是所谓的有界和无界流
。 - 提供分层API
越顶层越抽象,表达含义越简明,使用越方便;越底层越具体,表达能力越丰富,使用越灵活。
抽象程度由上往下:
- Flink SQL
- Flink Table API
- Flink DataStream API
- Flink ProcessFunction API
- Flink CEP API
- 支持事件时间(event-time)和处理时间(processing-time)语义。
- 精确一次(exactly-once)的状态一致性保证。
- 低延迟,每秒处理数百万个事件,毫秒级延迟。
- 与众多常用存储系统的连接。
- 高可用,动态扩展,实现7*24小时全天候运行。
1.2. Flink 和 Spark Streaming 对比
- 架构类型:
- Spark Streaming 是 微批次 处理框架。
- Flink 是标准的实时流处理引擎。
- 数据模型:
- spark 采用
RDD
模型,spark streaming 的 DStream 实际上也就是一组组小批数据 RDD 的集合 - Flink 基本数据模型是
数据流
,以及事件(Event
)序列
- 架构模型:
- Spark Streaming 在运行时的主要角色包括:Master、Worker、Driver、Executor。
- Flink 在运行 时主要包含:Jobmanager、Taskmanager 和 Slot。
- 任务调度:
- Spark Streaming 连续不断的生成微小的数据批次,构建有向无环图DAG,依次构建DStreamGraph,JobGenerator 和 JobScheduler。
- Flink 根据用户提交的代码生成 StreamGraph,经过优化生成 JobGraph,然后提交给 JobManager进行处理,JobManager 会根据 JobGraph 生成 ExecutionGraph, ExecutionGraph 是 Flink 调度最核心的数据结构,JobManager 根据 ExecutionGraph 对 Job 进行调度。
- 时间机制:
- Spark Streaming 支持的时间机制有限,只支持
处理时间
。 - Flink 支持了流处理程序在时间上的 三个定义:
处理时间、事件时间、注入时间
。同时也支持 watermark 机制来处理滞后数据。
- 容错机制:
- 对于 Spark Streaming 任务,我们可以设置 checkpoint,然后假如发生故障并重启,我们可以 从上次 checkpoint 之处恢复,但是这个行为只能使得数据不丢失,可能会重复处理,不能做到恰好一次处理语义。
- Flink 可以设置 checkpoint 和 savepoint,则使用两阶段提交协议来解决这个问题。
2. Filnk 运行架构
2.1. Yarn 任务运行流程
提交流程简图:
提交任务之前,先看 [Flink运行组件](#2.3. Flink 运行组件)。
- 提交任务,创建Flink Client,向 HDFS 上传依赖的Jar包和配置。
- Flink Client 上传完成之后,向 ResourceManager 提交 Job。
详见 [Flink Client](#2.3.1. Flink Client 客户端)。
Flink Client 提交的 Flink 代码,会被映射为 逻辑数据流dataflows
,类似于DAG。
它由三部分组成,即 Source(读取数据源),Transformation(算子加工) 和 sink(输出组件)。
dataflow 以至少一个sources开始至少一个sink结束,Transformation可以有任意个。
dataflow只是能够形成类似 DAG 的执行计划,并不能具体的体现为执行细节,比如并行度。Flink 采用执行图 ExecutionGraph
来描述具体的执行细节,构建 执行图 的步骤为:
client
上构建StreamGraph
根据 StreamAPI 代码生成的最初始的图,用于表示 程序 的拓扑结构。client
上构建JobGraph
优化 StreamGraph,生成 JobGraph,用于提交给 JobManager。- JobManager 上构建
Execution Graph
JobGraph 的并行化版本,是调度层最核心的数据结构。 - Task 上构建
物理执行图
最终在 Task 上执行 程序。
- ResourceManager 分配 Container 资源并通知对应的NodeManager 启动 ApplicationMaster。
ApplicationMaster 是运行 JobManager 的平台,目的是解藕 ResourceManager 和 JobManager - ApplicationMaster 启动后加载 Flink 的 Jar 包和配置构建环境,然后启动 JobManager。
详见 [JobManager](#2.3.2. JobManager 作业管理器)。
由Flink Client 构建的 JobGraph,在 JobManager 上进行进一步构建,主要是添加了并行结构,生成Execution Graph
。
- 并行度
在执行过程中,一个流 包含一个或多个分区,而每一个算子可以包含一个或多个子任务
(subTask),这些子任务在不同的线程、不同的容器中彼此互不依赖地执行。
Flink并行度可以从四个层面,优先级由低到高分别是:
- 系统层面
设置系统配置文件的parallelism.default = xxx
- 客户端层面
添加提交参数-p xxx
- 执行环境层面
env.setParallelism(xxx);
- 算子层面
oprator.setParallelism(xxx);
算子的种类可以分为两类:
One to One
比如 map,filter 和 flatMap 算子,上一个算子的子任务的元素个数和顺序直接进入下一个算子的子任务,这样的算子就是 One to One,类似于Spark的窄依赖。
Flink尽量将算子的subtask连接在一起组成oprator chain
算子链
。算子链形成条件:
- 上下游的并行度一致
- 下游节点只有唯一一个上游节点的输入
- 上下游节点共享一个 slot
- 下游节点的 chain 策略为 ALWAYS(例如 map、flatmap、filter等默认是ALWAYS)
- 上游节点的 chain 策略为 ALWAYS 或 HEAD(source默认是HEAD)
- 两个节点间数据分区方式是 forward
- 用户没有禁用 chain
算子链能减少线程之间的切换,减少消息的序列化/反序列化, 减少数据在缓冲区的交换,减少了延迟的同时提高整体的吞吐量。
- 相同并行度的连续两个Task,Flink尽量将他们放在同一个TaskManger上,task chain
任务链
,任务链能减少线程之间的切换和基于缓存区的数据交换,在减少时延的同时提升吞吐量。 Redistributing
上一个算子的子任务的元素个数和顺序会进入不同的新算子的子任务中,这样的算子就是 Redistributing,类似于Spark的宽依赖。会引起 Redistribute(重分区
)过程,类似于 Spark 的 Shuffle,具体分区类型如下:
规则 | 分区策略 | 使用场景 |
| 将数据通过轮询round-robin方式传递给下个分区 | 重分区默认规则 |
rescale | 在分区之前,rescale会对下游分区做分组,然后组内与 rebalance 类似,通过轮询方式传递给下游算子 | / |
| 上游数据key的hash值对下游分区数取模,再分配给下个分区,能保证key相同的数据进入同一个分区 | keyBy默认分区策略 |
| 将数据广播到下一个操作的所有实例 | Watermark分区策略 |
shuffle | 随机将数据发送到下一个操作的某个实例 | / |
forward | 直通到当前分区,模仿 OneToOne | / |
global | 不管下游有多少个分区,全局统一将所有数据传递给下游第一个分区(并行度为1,相当与放弃并行) | / |
partitionCustom | 用户自定义分区器 | / |
- 由 ApplicationMaster 通知资源所在节点的 NodeManager 启动 Container,Container中启动TaskManager。
- 申请到了资源,NodeManager 加载 Flink 的 Jar 包和配置构建环境并启动 TaskManager。
TaskManager 启动后向 JobManager 发送心跳包,并等待 JobManager 向其分配任务。
详见 [TaskManager](#2.3.4. TaskManager 任务管理器) - 在 TaskManger 的 slot 上启动 Task,并在 Task 中将 Executor Graph 转化为 物理只执行图。
补充:
任务调度详图:
2.2. Flink 线上部署
- standalone 模式
Flink提供的独立部署模式,一般做做测试可以。 - Yarn 模式
- Session-cluster模式
在 yarn 中初始化一个 flink 集群,开辟固定的资源
,以后提交任务都向这里提交。
所有的作业共享
Dispatcher(详见[Dispatcher](#2.3.5. Dispatcher 分发器)) 和 ReSourceManager(详见[ResourceManger](#2.3.3. ResourceManager 资源管理器)),如果资源满了,之后的Job作业无法提交,知道前面的任务释放资源才能提交。
优点是可以自己设置资源的最大值,但是很容易受固定资源的限制无法提及新作业,适合规模小执行时间短的作业。
- Per-Job-Cluster模式
每次提交Job作业,都向 Yarn单独申请
集群资源,所以 每个任务独享 Dispatch 和 ResourceManager,只要 Yarn 资源足够,可以提交满足需求的 Job 作业数量,任务执行完成,创建的 Flink 集群也会消失。
优点是资源上限是 Yarn 管理的所有资源,上限足够大,支持的任务数量也多。独享 Dispatch 和 ResourceManager,任务互相独立,互不影响。
2.3. Flink 运行组件
Flink四大组件,作业管理器(JobManager
)、资源管理器(ResourceManager
)、任务管理器(TaskManager
)以及分发器(Dispatcher
),都是运行在 JVM 上。
2.3.1. Flink Client 客户端
主要负责提供 Job 需要的 Jar 和 配置资源。
Flink Client提交完了 Job之后,后续计算不受影响,可以断开连接,或者维持连接等待结果更新。
2.3.2. JobManager 作业管理器
是Flink的Master,控制一个应用程序执行的主进程。
JobManager 会先接收到要执行的应用程序,这个应用程序会包括:作业图(JobGraph)、逻辑数据流图(logical dataflow graph)和打包了所有的类、库和其它资源的 JAR 包。JobManager 会把 JobGraph 转换成一个物理层面的 包含所有并发执行的 任务执行图(ExecutionGraph
)。
JobManager 会向资源管理器(ResourceManager)请求执行任务必要的资源,也就是任务管理器(TaskManager)上的插槽(slot),并且将分发执行图
到空闲的插槽上去。
在运行过程中,JobManager 会负责所有需要中央协调的操作,比如说检查点
(checkpoints)的协调。
2.3.3. ResourceManager 资源管理器
主要负责管理计算资源单元
,也就是任务管理器(TaskManager)的插槽(slot)。
当 JobManager 申请插槽资源时,ResourceManager 会将有空闲插槽的 TaskManager 分配给 JobManager。如果 ResourceManager 没有足够的插槽来满足 JobManager 的请求,它还可以向资源提供平台(Yarn)发起会话,以提供启动 TaskManager进程的容器。
另外,ResourceManager 还负责终止空闲的 TaskManager,释放计算资源。
2.3.4. TaskManager 任务管理器
是Flink的woker,主要负责提供 插槽 (slot)。
启动之后,TaskManager 会向资源管理器注册它的插槽;收到资源管理器的指令后,TaskManager 就会将一个或者多个插槽提供给 JobManager 调用。JobManager 就可以向插槽分配任务(tasks)来执行了。
在执行过程中,一个 TaskManager 可以跟其它运行同一应用程序的 TaskManager 交换数据
。
- TaskManager 和 Task Slot
Flink 中每一个 worker(TaskManager)都是一个JVM 进程
,它可能会在 独立的线程上执行一个或多个 subtask。为了控制一个 worker 能接收多少个 task,worker 通过task slot
来进行控制(一个 worker 至少有一个 task slot)。
TaskManager启动的时候,会根据相关参数将资源分为相应数量份,并且生成这个数量的 task slot。其中,每个 task slot 的内存资源相互隔离,但是不要求 CPU 隔离
,即线程启动。
同一个 TaskManager 的所有 task slot 组成一个 task group,统一个 task group 的所有task共用TaskManager的JVM
。这样在同一个 JVM 进程中的 task 将共享 TCP 网络连接(基于多路复用)和心跳消息。它们也可能共享数据集和数据结构,因此这减少了每个 task 的负载。
默认情况下,Flink 允许同一个Job的子任务共享 slot,极端情况下,一个 slot 可以保存Job整个管道。 - Task slot 和 parallelism并行度 的区别
Task Slot 是静态的概念,是指 TaskManager 具有的最大
并发执行能力,可以通过参数 taskmanager.numberOfTaskSlots 进行配置。
而 并行度 parallelism 是动态概念,即 TaskManager 运行程序时实际
使用的并发能力,可以通过参数 parallelism.default 进行配置。
2.3.5. Dispatcher 分发器
负责为应用程序提供 rest 接口
,实现跨作业运行
。
当一个应用被提交执行时,分发器就会启动并将应用移交给一个 JobManager。
Dispatcher 也会启动一个 Web UI,用来方便地展示和监控
作业执行的信息。
3. Flink API
3.1. Transform 算子
- Map
- flatMap
- Filter
- KeyBy
DataStream -> KeyStream
- sum( )
- min( )
- max( )
- minBy( )
- maxBy( )
- reduce( )
- Split
DataStream → SplitStream,根据某些特征把一个 DataStream 拆分成两个或者多个 DataStream。 - Select
SplitStream → DataStream:从一个 SplitStream 中获取一个或者多个DataStream。 - Connect
DataStream, DataStream → ConnectedStreams:连接两个保持他们类型的数据流,两流被 Connect 之后,只是被放在了一个同一个流中,内部依然保持各自的数据和形式不发生任何变化,两流相互独立。 - CoMap, CoFlatMap
ConnectedStreams → DataStream:作用于 ConnectedStreams 上,功能与 map和 flatMap 一样,对 ConnectedStreams 中的每一个 Stream 分别进行 map 和 flatMap处理。 - Union
Union 和 Connect 区别
- Union 之前两个流的类型必须是一样,Connect 可以不一样,在之后的 coMap中再去调整成为一样的。
- Connect 只能操作两个流,Union 可以操作多个
3.2. Window 窗口
流式计算的数据特点是源源不断,有时候我们希望对数据流做有限处理,特别是时间,WIndow就是一种 切割无限数据为有限块进行处理的手段。
Window 将一个无限的 stream 拆分成有限大小的 buckets 桶,我们可以在这些桶上做计算操作。
WIndow 可以分为如下:
- CountWindow:按照指定的数据条数生成一个 Window,与时间无关。
- TimeWindow:
按照时间生成 Window。
滚动窗口
(Tumbling Window)
将数据依据固定的窗口长度对数据进行切片。
特点:时间对齐,窗口长度固定,没有重叠。
适用场景:适合做 BI 统计等(做每个时间段的聚合计算)。滑动窗口
(Sliding Window)
滑动窗口是固定窗口的更广义的一种形式,滑动窗口由固定的窗口长度和滑动间隔组成。
特点:时间对齐,窗口长度固定,可以有重叠。
适用场景:对最近一个时间段内的统计(求某接口最近 5min 的失败率来决定是否要报警)。会话窗口
(Session Window)
由一系列事件组合一个指定时间长度的 timeout 间隙组成,类似于 web 应用的 session,也就是一段时间没有接收到新数据就会生成新的窗口。
特点:时间无对齐。
3.3. 时间语义
- Event Time
是事件创建的时间。它通常由事件中的时间戳描述,例如采集的日志数据中,每一条日志都会记录自己的生成时间,Flink 通过时间戳分配器访问事件时间戳。
大部分情况下使用 Event Time,通过如下设置时间语义env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
- Ingestion Time:是数据进入 Flink 的时间。
- Processing Time:是每一个执行基于时间操作的算子的本地系统时间,与机器相关,默认的时间属性就是 Processing Time。
3.4. Watermark
3.4.1. Watermark 产生背景
流处理从事件产生,到流经 source,再到 operator,中间是有一个过程和时间的,大部分情况下,流到 operator 的数据是按照Event Time事件时间顺序来的,但是也不排除由于网络、分布式等原因,导致乱序的产生,所谓乱序,就是指 Flink 接收到的事件的先后顺序不是严格按照事件的 Event Time 顺序排列的。
一旦出现乱序,如果只根据 eventTime 决定 window 的运行,我们不能明确数据是否全部到位,但又不能无限期的等下去,所以 Flink 提供了 Watermark
机制来确保迟到数据的收集。
Watermark 目的是控制乱序数据时间窗口的开启和关闭时机,从而达到容忍一定范围内的乱序数据延迟进入正确但是本该关闭的窗口。
3.4.2. Watermark 工作机制
- 设置 Event Time 时间语义,并设置 Watermark 机制,并指定 Watermark 延迟时间 t。
- 乱序数据流源源不断进入Flink,Flink会定时检验截止现在最大的 EventTime,即
MaxEventTime
,并计算 Watermark,即MaxEventTime -t
。 - 如果存在开始时间为
MaxEventTime
的窗口,则开启该窗口。 - 如果存在结束时间为
MaxEventTime - t
的窗口,则关闭该窗口。 - Watermark只控制窗口开闭,具体的数据进入哪个窗口还是看
EventTime
。
Watermark 通过设置 延迟时间 ,巧妙的延迟了窗口关闭的时间,这让因为乱序而迟到且在延迟时间范围内的数据能赶在正确窗口没有关闭前进入窗口,大大增强数据可靠性。
3.4.3. Watermark 任务间传递
以上说的都是在一个分区的 Watermark 数据流转,但是Flink程序是支持并行的,即可能存在多个subTask同时执行,每个 subTask 都会有一条 Watermark 处理线,当发生 Redistribute 重分区时,就会有多个 Watermark,多个Watermark肯定要选择出来一个 Watermark 传递给下游任务,传递细节如下:
- 每个 subTask 都将自己的 Watermark 广播 broadcast 给下游 subTask 子任务,下游分别保存上游各个子任务广播的 Watermark。
- 为了确保数据到齐,下游子任务比较各个Watermark 并选择最小的 watermark 作为自己的 Watermark。
3.4.4. 延迟数据补充方案
侧输出流,对于迟到的数据,设置合理的 Watermark 延迟关闭窗口时间,可以保证绝大部分迟到数据进入正确的时间窗口。但是对于某些迟到时间过长的数据,Watermark可能无法支持其进入正确窗口,这是可以使用 侧输出流
收集迟到时间过长的数据,保证数据一致性。
测输出流可以通过 OutputTag
来定义,并在合适时机通过 getSideOutput()
来获取侧输出流。
3.5. 状态编程
流式计算分为无状态和有状态两种情况。无状态的计算观察每个独立事件,并根据最后一个事件输出结果。有状态的计算则会基于多个事件,可能用到历史流数据输出结果。
有状态流处理需要维护所有已处理记录的状态值
,并根据每条新输入的记录更新状态,输出记录能够反映综合考虑多个事件之后的结果。
Flink提供了三种状态存储方式:MemoryStateBackend、FsStateBackend、 RocksDBStateBackend。
3.5.1. 状态类型
算子状态(operator state)
算子状态的作用范围限定为算子任务
。这意味着由同一并行任务所处理的所有数据都可以访问到相同的状态,同一任务的状态是共享的
。算子状态不能由相同或不同算子的另一个任务访问。
- 列表状态(List state)
将状态表示为一组数据的列表。 - 联合列表状态(Union list state)
也将状态表示为数据的列表。它与常规列表状态的区别在于,在发生故障时,或者从保存点(savepoint)启动应用程序时如何恢复。 - 广播状态(Broadcast state)
如果一个算子有多项任务,而每项任务状态又都相同,那么这种特殊情况最适合应用广播状态。
键控状态(keyed state)
Keyed State 很类似于一个分布式的 key-value map 数据结构,只能用于 KeyedStream 也就是 keyBy 算子处理之后。键控状态是根据输入数据流中定义的键(key)来维护和访问的,所以他是 同key共享的状态。
- ValueState保存单个的值,值的类型为 T。
- ListState保存一个列表,列表里的元素的数据类型为 T。
- MapState<K, V>保存 Key-Value 对。
3.5.2. 状态一致性
在流处理中,一致性可以分为 3 个级别:
- at-most-once:
计数结果可能丢失。 - at-least-once:
计数程序在发生故障后可能多算,但是绝不会少算。 exactly-once
:
这指的是系统保证在发生故障后得到的计数结果与正确值一致。
所谓的状态一致性,要保证以下两点:
- 每个状态既不能丢失,也不能重复计算,保证完全正确,即实现 exactly-once。
- 在遇到故障时可以恢复状态,恢复以后的重新计算,结果应该也是完全正确的。
Flink 的一个重大价值在于,它既保证了 exactly-once,也具有低延迟和高吞吐的处理能力。
3.5.3. Flink 内部一致性
Flink 内部一致性 exactly-once 的保证,其实是依靠 checkpoints 一致性检查点(详见[一致性检查点](#3.6.1. 一致性检查点))实现的。
3.5.4. end to end 端到端一致性
Flink 流处理器内部得到了保证的,但是实际使用,流处理应用除了流处理器以外还包含了数据源(例如 Kafka)和输出持久化系统(比如说ClickHouse)。
端到端的一致性保证,意味着结果的正确性贯穿了整个流处理应用的始终;每一个组件都保证了它自己的一致性,整个端到端的一致性级别取决于所有组件中一致性最弱的组件。具体可以划分如下:
- source 端 —— 需要外部源可重设数据的读取位置
- 内部保证 —— 依赖 checkpoint 一致性检查点
- sink 端 —— 需要保证从故障恢复时,数据不会重复写入外部系统而对于 sink 端,又有两种具体的实现方式:
幂等
(Idempotent)写入和事务性
(Transactional)写入。
- 幂等写入
Flink 只保证不丢失数据,可能重复输出,Sink端自己实现同一条数据去重即幂等性,比如ClickHouse 的ReplacingMergeTree
表引擎。 - 事务写入
涉及分布式事务,需要构建事务来写入外部系统,构建的事务对应着 checkpoint,等到 checkpoint 真正完成的时候,才把所有对应的结果写入 sink 系统中。
分布式事务,一般有两种可靠实现,分别是预写日志WAL
和两阶段提交
。
DataStream API 提供了 GenericWriteAheadSink 模板类和 TwoPhaseCommitSinkFunction 接口,可以方便地实现这两种方式的事务性写入。
- 预写日志
- 2PC两阶段提交
分布式事务的经典方案,事务的发起者这里是Flink作为事务协调者
,事务的执行者这里是 Sink端的服务作为事务参与者
。流程为分两个阶段提交事务,分别是:
- 预提交
Flink将数据发送给 Sink端 的所有服务节点,并创建临时的预提交目录
,保存提交的状态信息。 - 实际提交
Sink端所有节点都保存数据,并都请求提交事务,Flink做分布式事务提交,即创建实际保存事务目录
,并拷贝预提交目录数据并删除与提交目录。只要有一个Sink端节点返回保存失败,Flink都会促发分布式事务回滚,删除临时目录。
3.6. 容错机制
3.6.1. checkpoint 一致性检查点
保存点 checkpoint 就是将数据进行持久化存储,遇到节点宕机情况重启后,可以恢复到上一个checkpoint 保存时的状态,是 Flink 保证内部状态一致性和容灾的关键手段。checkpoint 容错机制细节如下:
- 存储 checkpoint:
Spark 也有 checkpoint,Spark 的 checkphint 会将完成Task后的所有分区数据都存储下来,数据量可能相当大。Flink 的 checkpoint 它只保存所有任务都恰好处理完一个相同的输入数据的时候
的所有状态 及其这个相同输入数据
作为key,一定是所有任务都处理完的那个流数据,否则无法保证其它任务是否未来得及或者已经计算下一个流数据。数据量相对少很多。
Spark 的 checkpoint 是会阻碍计算进行的,因为要先保证数据保存成功,才能被下游任务使用,性能开销大。Flink 的 checkpoint 相对而言性能开销很小,只需要记录输入数据作为以及处理完这个输入数据的所有任务的状态。 - 恢复 checkpoint:
Flink宕机重启后,得益于 checkpoint 保存的key,Flink的各个任务根据通过恢复 checkpoint 可以获取上一次执行到的流数据位置,再恢复所有任务的状态,实现精确恢复某个数据处理完的计算状态。接下来只需要从 checkpoint 完成的那个流数据的下一个数据开始计算,就是正常的计算了。
3.6.2. savepoint 保存点
原则上,创建保存点使用的算法与检查点完全相同,因此保存点可以认为就是具有一些额外元数据的检查点,区别是 checkpoint 一般由 Flink 内部自动创建,而 savepoint 往往是由用户自己手动保存,也可以手动指定恢复。
常用场景有 手动备份,更新升级重启等需要暂停集群的时候,可以使用 savepoint 手动保存当前状态,等到重启时恢复数据。
4. Flink 知识点
4.1. Flink 如何实现批流一体
Flink将为批处理是流处理的一种特殊情况,即批处理是有限的流处理。
Flink 提供 DataSet API库,用以处理批数据。提供 DataStream API库,用以处理流数据,从而实现实现所谓的批流一体。
4.2. Flink 内存管理与对象[反]序列化
Flink使用大量的堆外内存来保存对象,从而避免堆内频繁的GC,提高性能。Flink堆外内存主要分为三类:
- Network Buffers:
在TaskManager启动的时候分配的,用于缓存网络数据的内存,每个块是 32K,默认分配2048个。 - Memory Manager pool:
大量的Memory Segment块,用于运行时的算法(Sort/Join/Shuffle等)。 - User Code:
用于User code和TaskManager本身的数据结构。
Java本身自带的序列化和反序列化的功能,但是辅助信息占用空间比较大,在序列化对象时记录了过多的元信
息。Flink以独特的方式处理数据类型和序列化,通过不同的 TypeInformation
, 来[反]序列化器。
4.3. Flink延迟高调优因素
- 资源调优:
资源调优即是对作业中的Operator的并发数(parallelism)、CPU(core)、堆内存(heap_memory)等 参数进行调优。 - 参数调优:
并行度的设置,State的设置,checkpoint的设置。
4.4. Flink 数据交换机制
- 同一个 Task 内部的数据交换
同一个 Task 内部由 oprator chain 算子链组成,下一个算子直接获取上一个算子的数据,无需序列化和网络IO的开销,性能相当高。 - 不同 Task 相同 TaskManager 之间数据的交换
使用堆外内存Network Buffers
,将上一个 TaskA 计算完成的数据缓存到 Network Buffers 当中,位于相同 TaskManger 上的下一个 TaskB 共享 Network Buffers,直接读取数据。 - 不同 Task 不同 TaskManager 之间数据的交换
上一个 TaskA 数据完成计算之后,缓存到本地 TaskManagerA 的堆外内存 Network BuffersA 中。下一个TaskB 的 TaskManagerB 来拉取 Network BuffersA 的数据到本地的 Network BuffersB 中,供 TaskB 读取。
显然,不同 Task 不同 TaskManger 之间的数据交换,不仅需要经过两次堆外内存 Network Buffers 的缓存,还有额外的网络IO成本,较前面两种数据交换方式开销很大。一种常见的优化手段,就是将并行度相同
,特别是One To One依赖 或者 forward 分区机制 的相邻Task组成 task chain任务链
。这样就由 不同 Task 不同 TaskManager 转化为 不同 Task 相同 Task Manager情况了,提高效率。
4.5. Flink 反压
数据管道中某个消费节点消费能力不足,处理速率跟不上上游发送数据的速率,而需要对上游进行限速,即反压。对于不同的流处理框架,反压机制不同,如:
- Flink 反压
Flink 基于 producer-consumer生产者消费者模型
来进行消息传递的,反压也是一样。Flink 使
用了高效有界的分布式阻塞队列,下游消费者消费变慢,上游就会受到阻塞。 - Storm 反压
Storm 是通过监控 Bolt 中的接收队列负载情况,如果超过高水位值就会将反压信息写到 Zookeeper ,
Zookeeper 上的 watch 会通知该拓扑的所有 Worker 都进入反压状态。
Flink 和 Storm 反压的最大不同在于,Flink 是逐级反压,而Strom是从源头减少数据生产的。
4.6. Flink SQL 的实现
Flink 将 SQL 校验、SQL 解析以及 SQL 优化交给了Apache Calcite。
- Calcit 对 Flink SQL 进行语法
校验
。 - Calcit 将通过校验 Flink SQL 构建
抽象语法树
。 - Calcit 通过抽象语法树,形成
逻辑计划树
。 - Calcit 读取 Flink自定义的优化规则,生成并优化
物理计划树
。 - Flink 根据 物理计划树,生成
DataStream API
代码,并提交Flink平台执行。