[Flink] Apache Flink 概述
0 缘起
- 工作中接触和使用Flink,也有2年多了。是时候沉淀些这方面的实践总结了。
Flink 在阿里云的生态位

1 概述:Apache Flink 的定义、架构及原理
什么是 Flink?
- Apache Flink 是一个分布式大数据处理引擎,可对有限数据流和无限数据流进行有状态或无状态的计算,能够部署在各种集群环境,对各种规模大小的数据进行快速计算。

- Flink 是一个以 流 为核心的高可用、高性能的分布式计算引擎。
其具备 流批一体,高吞吐、低延迟,容错能力,大规模复杂计算等特点,在数据流上提供 数据分发、通信等功能。
Flink 的基础理念:数据流、流批一体、容错机制
-
数据流:所有产生的 【数据】 都天然带有 【时间】概念,把 【事件】 按照时间顺序排列起来,就形成了一个【事件流】,也被称作【数据流】。
-
流批一体:
首先,必须先明白什么是 有界数据 和 无界数据:

- 有界数据:就是在一个确定的时间范围内的数据流,有开始、有结束,一旦确定就不会再改变,一般 批处理 用来处理有界数据。(如上图的 bounded stream)
- 无界数据:就是持续产生的数据流,数据是无限的,有开始、无结束,一般 流处理 用来处理无界数据。(如图 unbounded stream)
- 容错能力:在分布式系统中,硬件故障、进程异常、应用异常、网络故障等异常无处不在,Flink 引擎必须保证故障发生后 不仅可以 重启应用程序,还要 确保其内部状态保持一致,从最后一次正确的时间点重新出发。
- Flink 提供 集群级容错 和 应用级容错 能力:
- 集群级容错: Flink 与 集群管理器 紧密连接,如 YARN、Kubernetes,当进程挂掉后,自动重启新进程接管之前的工作。同时具备 高可用性 ,可消除所有单点故障,
- 应用级容错:Flink 使用 轻量级分布式快照,设计检查点(
checkpoint)实现可靠容错。
Flink 利用检查点特性,在框架层面提供
Exactly-once语义,即端到端的一致性,确保数据仅处理一次,不会重复也不会丢失,即使出现故障,也能保证数据只写一次。
Flink 的发展历程

-
Apache Flink 是一个开源的流处理框架,最初由柏林工业大学的博士生在一个名为 StratoSphere 的大数据研究项目中开发。Flink 最早是一个批处理引擎,2014 年被捐赠给 Apache 基金会,并迅速成为 Apache 的顶级项目之一。
-
早期发展
- 2014 年:Flink 0.6.0 版本发布,标志着 Flink 的正式推出。同年,Flink 0.7 版本发布,推出了 DataStream API,这是目前 Flink 应用最广泛的 API。
- 2015 年:Flink 0.9 版本引入了内置的 State 支持和 Global Checkpoint 机制,解决了流计算系统中状态管理和一致性的问题。

- 关键版本和功能
- Flink 1.0:引入了基于事件时间的计算支持和 Watermark 机制,能够高效处理乱序和迟到数据。还内置了各种窗口支持,如滚动窗口、滑动窗口和会话窗口。

- Flink 1.9:进行了较大的架构调整,
Table API和DataStream API成为同级 API,并引入了统一的Catalog API和Blink planner,增强了 SQL 的执行能力。
- 阿里巴巴的贡献
- 阿里巴巴在 2015 年开始使用 Flink,并对其进行了大量改进,包括重构分布式架构、引入增量 Checkpoint 机制和基于信用的流控机制。阿里还在 2019 年将内部的 Blink SQL 贡献给了 Flink 社区,推动了 Flink SQL 的发展。
- 全球化和未来发展
- Flink 已成为全球范围内实时流计算的事实标准,吸引了众多企业和开发者的参与。
- 阿里巴巴和 Ververica 主导了
Flink的许多核心改进,并推出了全球统一的 Flink 企业版平台Ververica Platform。- 未来,Flink 将继续在流批一体和流式数仓方向上发展,进一步提升实时计算的能力。

- 推荐文献
Flink 和 Spark Streaming 的区别?
- Flink 和 Spark Sreaming 最大的区别在于:
- Flink 是标准的实时处理引擎,基于事件驱动,以流为核心;
- 而 Spark Streaming 的
RDD实际是一组小批次的 RDD 集合,是微批(Micro-Batch)的模型,以批为核心。
下面我们介绍两个框架的主要区别:
架构模型
Spark Streaming在运行时的主要角色包括:
- 服务架构集群和资源管理: Master / Yarn Application Master;
- 工作节点: Work / Node Manager;
- 任务调度器: Driver;任务执行器: Executor

Flink在运行时主要包含:客户端 Client、作业管理 Jobmanager、任务管理 Taskmanager。

任务调度
Spark Streaming连续不断的生成微小的数据批次,构建有向无环图DAG
Spark Streaming 会依次创建 DStreamGraph、JobScheduler。

Flink:
- 根据用户提交的代码生成
StreamGraph,经过优化生成JobGraph;然后,提交给JobManager进行处理- JobManager 会根据 JobGraph 生成
ExecutionGraph
ExecutionGraph是 Flink 调度最核心的数据结构
- JobManager 根据 ExecutionGraph 对 Job 进行调度,根据物理执行图部署到Taskmanager上形成具体的 Task 执行。

时间机制
- Spark Streaming 支持的时间机制有限,只支持 处理时间。
- Flink 支持了流处理程序在时间上的三个定义:事件时间 EventTime、摄入时间 IngestionTime 、处理时间 ProcessingTime。同时也支持 watermark 机制来处理滞后数据。

容错机制
- 对于 Spark Streaming 任务,我们可以设置
checkpoint,然后假如发生故障并重启,我们可以从上次checkpoint之处恢复,但是这个行为只能使得数据不丢失,可能会重复处理,不能做到恰好一次处理语义。 - Flink 则使用 两阶段提交协议 来解决这个问题。
Flink Application 核心机制
- 了解 Flink 应用开发需要先理解 Flink 的 Streams、State、Time 等基础处理语义以及 Flink 兼顾灵活性和方便性的多层次 API。
Streams/数据流
- Streams,流,分为有限数据流与无限数据流
unbounded stream是有始无终的数据流,即无限数据流;bounded stream是限定大小的有始有终的数据集合,即有限数据流- 二者的区别在于无限数据流的数据会随时间的推演而持续增加,计算持续进行且不存在结束的状态,相对的有限数据流数据大小固定,计算最终会完成并处于结束的状态。
State/状态
- State,状态是计算过程中的数据信息,在容错恢复和 Checkpoint 中有重要的作用,流计算在本质上是 Incremental Processing,因此需要不断查询保持状态;另外,为了确保 Exactly- once 语义,需要数据能够写入到状态中;而持久化存储,能够保证在整个分布式系统运行失败或者挂掉的情况下做到 Exactly- once,这是状态的另外一个价值。
Time/时间
- Time,分为 Event time、Ingestion time、Processing time
- Flink 的无限数据流是一个持续的过程,时间是我们判断业务状态是否滞后,数据处理是否及时的重要依据。
API/接口
- API,API 通常分为三层,由上而下可分为 SQL / Table API、DataStream API、ProcessFunction 三层
- API 的表达能力及业务抽象能力都非常强大,但越接近 SQL 层,表达能力会逐步减弱,抽象能力会增强;
- 反之,ProcessFunction 层 API 的表达能力非常强,可以进行多种灵活方便的操作,但抽象能力也相对越小。

-
Flink API
-
Flink SQL
- 不同于诸多开源的提供非常底层的、编程API的流式数据处理系统,实时计算 Flink提供更加高层、更加面向业务化的Flink SQL(标准SQL语法上提供了关于流式处理的语法扩展)。
- Flink SQL能够方便数据开发人员使用标准化的SQL,完成流式数据计算加工的业务流程。
因此,实时计算 Flink适合更大众的数据分析人员快速、方便地完成一个流式数据处理业务。
Flink 程序结构 : Source + Transform + Sink
Flink程序的基本构建块是流和转换。从概念上讲,流是(可能永无止境的)数据记录流,而转换是将一个或多个流作为一个或多个流的操作。输入,并产生一个或多个输出流。
请注意,Flink 的 DataSet API 中使用的 DataSet 也是内部流 。

- Flink 应用程序结构: (如上图所示)
Source: 数据源。Flink 在流处理和批处理上的 source 大概有 4 类:基于本地集合的 source、基于文件的 source、基于网络套接字的 source、自定义的 source。自定义的 source 常见的有 Apache kafka、RabbitMQ 等,当然你也可以定义自己的 source。
Transformation:数据转换的各种操作/算子
有 Map / FlatMap / Filter / KeyBy / Reduce / Fold / Aggregations / Window / WindowAll / Union / Window join / Split / Select等,操作很多,可以将数据转换计算成你想要的数据。
Sink:接收器(输出器),Flink 将转换计算后的数据发送的地点 ,你可能需要持久化/存储下来
- Flink 常见的 Sink 大概有如下几类:写入文件、打印出来、写入 socket 、自定义的 sink 。
自定义的 sink 常见的有 Apache kafka、RabbitMQ、MySQL、ElasticSearch、Apache Cassandra、Hadoop FileSystem 等,同理你也可以定义自己的 sink。
Flink Architecture
Flink的架构优势
- 在架构部分,主要分为以下4点:

-
第一, Flink 具备统一的框架处理有界数据流和无界数据流2种数据流的能力
-
第二, 部署灵活。Flink 底层支持多种资源调度器,包括 Yarn、Kubernetes 等。
Flink 自身带的 Standalone 的调度器,在部署上也十分灵活。
-
第三, 极高的可伸缩性,可伸缩性对于分布式系统十分重要,阿里巴巴双 11 大屏采用 Flink 处理海量数据,使用过程中测得 Flink 峰值可达 17 亿 / 秒。
-
第四, 极致的流式处理性能。Flink 相对于
Storm最大的特点是将状态语义完全抽象到框架中,支持本地状态读取,避免了大量网络 IO,可以极大提升状态存取的性能。
总体架构



任务调度与部署执行模型
Job Client / Job Manager / Task Manager / Actor System / ...

-
当 Flink 执行 executor 会自动根据程序代码生成 DAG 数据流图;
-
ActorSystem 创建 Actor 将数据流图发送给 JobManager 中的 Actor;
-
JobManager 会不断接收 TaskManager 的心跳消息,从而可以获取到有效的 TaskManager;
-
JobManager 通过调度器在 TaskManager 中调度执行 Task(在 Flink 中,最小的调度单元就是 task,对应就是一个线程);
-
在程序运行过程中,task 与 task 之间是可以进行数据传输的。
Job Client
-
主要职责是提交任务, 提交后可以结束进程, 也可以等待结果返回;
-
Job Client 不是 Flink 程序执行的内部部分,但它是任务执行的起点;
-
Job Client 负责接受用户的程序代码,然后创建数据流,将数据流提交给 Job Manager 以便进一步执行。 执行完成后,Job Client 将结果返回给用户。
Job Manager
- 主要职责是调度工作并协调任务做检查点;
- 集群中至少要有一个 master,master 负责调度 task,协调 checkpoints 和容错;
- 高可用设置的话可以有多个 master,但要保证一个是 leader, 其他是 standby;
- Job Manager 包含 Actor System、Scheduler、CheckPoint 三个重要的组件;
- Job Manager 从客户端接收到任务以后, 首先生成优化过的执行计划, 再调度到 TaskManager 中执行。
Task Manager
- 主要职责是从 Job Manager 处接收任务, 并部署和启动任务, 接收上游的数据并处理;
- Task Manager 是在 JVM 中的一个或多个线程中执行任务的工作节点;
- Task Manager 在创建之初就设置好了 Slot, 每个 Slot 可以执行一个任务。
Operator(算子)、Operator Chain(算子链)、Task(任务/线程)
- Operator(算子)
详情参见:本文档: Stream 章节。
Source Operator / Transformation Operator / Sink Operator
- (Operator Chain)算子链的定义
Flink 算子链(
Operator Chain)是一种优化机制。
客户端在提交任务时————生成JobGraph阶段,会对代码中可进行优化合并的 Operator 进行优化操作,它将多个算子(Operator)串联在一起,优化成一个算子链(Operator Chains)以放到一个task(同一个线程)中执行,从而减少数据交换、序列化和上下文切换/线程切换、缓冲的开销,提高作业的执行效率。
下面以官网中的例子进行说明,如下图所示:

图中,source、map、[keyBy|window|apply]、sink算子的并行度分别是2、2、2、2、1,经过Flink优化后,source和map算子组成一个算子链,作为一个task运行在一个线程上,其简图如图中condensed view所示,并行图如parallelized view所示。
- 算子之间是否可以组成一个
Operator Chains,看是否满足以下条件:
- 上下游算子的并行度一致
- 下游节点的入度为1
- 上下游节点都在同一个 slot group 中
上下游算子实例处于同一个 SlotSharingGroup 中。
- 下游节点的 chain 策略为 ALWAYS(可以与上下游链接,map、flatmap、filter等默认是ALWAYS)
下游算子的链接策略(ChainingStrategy)为 ALWAYS。
- 上游节点的 chain 策略为 ALWAYS 或 HEAD(只能与下游链接,不能与上游链接,Source默认是HEAD)
上游算子的链接策略为 HEAD 或 ALWAYS。
- 2个节点间数据分区方式是 forward
2个算子间的物理分区逻辑是 ForwardPartitioner
- 2个算子间的
shuffle方式不是批处理模式
- 用户没有禁用 chain(代码中是否配置
disableChain())
若是对此还有疑惑,可以参看Operator Chains一文
- 禁用算子链
用户可以通过以下方法禁用算子链:
- 在算子上调用
disableChaining()方法。
// 设置算子链
sum.print().disableChaining();
- 在算子上调用
startNewChain()方法,强制开始一个新的算子链。- 调用
StreamExecutionEnvironment.disableOperatorChaining()方法,在整个运行时环境中禁用算子链。
// 禁用算子链
env.disableOperatorChaining();
任务槽(Task Slot)、槽共享机制

TaskManager 与 JVM 的关系 / 线程 与 子任务的关系
- 每个
TaskManager是一个独立的JVM进程, 可以在不同的线程中执行一个或多个子任务。
任务槽(Task Slot)
-
为了控制一个
worker能接收多少个task————worker通过task slot来进行控制(一个 worker 至少有一个task slot)。 -
每个
task slot表示TaskManager拥有资源的一个固定大小的子集。 -
flink 将进程的内存进行了划分到多个
slot中。
图中有 2 个 TaskManager,每个 TaskManager 有 3 个 slot 的,每个 slot 占有 1/3 的内存。
- 内存被划分到不同的 slot 之后可以获得如下好处:
TaskManager最多能同时并发执行的任务是可以控制的,那就是3个,因为不能超过 slot 的数量。slot有独占的内存空间,这样在一个TaskManager中可以运行多个不同的作业,作业之间不受影响。
槽共享(同一作业的子任务间)
- 默认情况下,Flink 允许【子任务】共享插槽,即使它们是不同任务的子任务,只要它们来自同一个作业。
结果是一个槽可以保存作业的整个管道。允许插槽共享有2个主要好处:
- 只需计算 Job 中最高并行度(
parallelism)的task slot,只要这个满足,其他的 job 也都能满足。- 资源分配更加公平,如果有比较空闲的 slot 可以将更多的任务分配给它。
图中若没有任务槽共享,负载不高的 Source/Map 等
subtask将会占据许多资源,而负载较高的窗口subtask则会缺乏资源。
- 有了任务槽共享,可以将基本并行度(
base parallelism)从 2 提升到 6,提高了分槽资源的利用率。同时它还可以保障TaskManager给subtask的分配的 slot 方案更加公平。

Flink Operation(运维/监控/容错)
-
后面会有专门讲解,此处简单分享 Flink 关于运维及业务监控的内容:
-
Flink 具备 7 X 24 小时高可用的
SOA(面向服务架构)
- 原因:在实现上 Flink 提供了一致性的
Checkpoint。Checkpoint是 Flink 实现容错机制的核心————它周期性的记录计算过程中Operator的状态,并生成快照持久化存储。- 当 Flink 作业发生故障崩溃时,可以有选择的从
Checkpoint中恢复,保证了计算的一致性。
- Flink 本身提供监控、运维等功能或接口,并有内置的
Web UI,对运行的作业提供DAG图以及各种Metric等,协助用户管理作业状态。
Flink 的应用场景
Flink 的应用场景:Data Pipeline

- Data Pipeline 的核心场景
类似于数据搬运并在搬运的过程中进行部分数据清洗或者处理,而整个业务架构图的左边是 Periodic ETL,它提供了流式 ETL 或者实时 ETL,能够订阅消息队列的消息并进行处理,清洗完成后实时写入到下游的 Database 或 File system 中。场景举例:
- 实时数仓
- 当下游要构建实时数仓时,上游则可能需要实时的
Stream ETL。- 这个过程会进行实时清洗或扩展数据,清洗完成后写入到下游的实时数仓的整个链路中,可保证数据查询的时效性,形成实时数据采集、实时数据处理以及下游的实时 Query。
- 搜索引擎推荐
- 搜索引擎这块以淘宝为例,当卖家上线新商品时,后台会实时产生消息流,该消息流经过 Flink 系统时会进行数据的处理、扩展。
- 然后,将处理及扩展后的数据生成实时索引,写入到搜索引擎中。
这样当淘宝卖家上线新商品时,能在秒级或者分钟级实现搜索引擎的搜索。
- ...
Flink 应用场景:Data Analytics

- Data Analytics,如图,左边是 Batch Analytics,右边是 Streaming Analytics。
- Batch Analysis 就是传统意义上使用类似于 Map Reduce、Hive、Spark Batch 等,对作业进行分析、处理、生成离线报表
- Streaming Analytics 使用流式分析引擎如 Storm,Flink 实时处理分析数据,应用较多的场景如实时大屏、实时报表。
Flink 应用场景:Data Driven

-
从某种程度上来说,所有的实时的数据处理或者是流式数据处理都是属于
Data Driven,流计算本质上是Data Driven计算。 -
应用较多的如:风控系统,当风控系统需要处理各种各样复杂的规则时,Data Driven 就会把处理的规则和逻辑写入到 Datastream 的 API 或者是 ProcessFunction 的 API 中,然后将逻辑抽象到整个 Flink 引擎中,当外面的数据流或者是事件进入就会触发相应的规则,这就是 Data Driven 的原理。
-
在触发某些规则后,Data Driven 会进行处理或者是进行预警,这些预警会发到下游产生业务通知,这是 Data Driven 的应用场景,Data Driven 在应用上更多应用于复杂事件的处理。
深度解读:有状态的流式处理
传统批处理

- 传统批处理方法是持续收取数据,以时间作为划分多个批次的依据,再周期性地执行批次运算。
但假设需要计算每小时出现事件转换的次数,如果事件转换跨越了所定义的时间划分,传统批处理会将中介运算结果带到下一个批次进行计算;
除此之外,当出现接收到的事件顺序颠倒情况下,传统批处理仍会将中介状态带到下一批次的运算结果中,这种处理方式也不尽如人意。
理想方法

- 第一,要有理想方法,这个理想方法是引擎必须要有能力可以累积状态和维护状态。
累积状态代表着过去历史中接收过的所有事件,会影响到输出。
-
第二,时间。时间意味着引擎对于数据完整性有机制可以操控,当所有数据都完全接受到后,输出计算结果。
-
第三,理想方法模型需要实时产生结果,但更重要的是采用新的持续性数据处理模型来处理实时数据,这样才最符合 continuous data 的特性。
流式处理

- 流式处理,简单来讲,即:有一个无穷无尽的数据源在持续收取数据,以代码作为数据处理的基础逻辑,数据源的数据经过代码处理后产生出结果,然后输出,这就是流式处理的基本原理。
分布式流式处理

- 假设
Input Streams有很多个使用者,每个使用者都有自己的ID。
如果计算每个使用者出现的次数,我们需要让同一个使用者的出现事件流到同一运算代码,这跟其他批次需要做
group by是同样的概念。
所以,跟Stream一样需要做分区,设定相应的 key,然后让同样的key流到同一个computation instance做同样的运算。
有状态分布式流式处理

如图,上述代码中定义了变数 X,X 在数据处理过程中会进行读和写,在最后输出结果时,可以依据变数
X决定输出的内容,即状态X会影响最终的输出结果。
- 这个过程中,第一个重点是先进行了状态
co-partitionedkey by,同样的 key 都会流到computation instance,与使用者出现次数的原理相同,次数即所谓的状态,这个状态一定会跟同一个 key 的事件累积在同一个 computation instance。

相当于: 根据输入流的
key重新分区的 状态,当分区进入stream之后,这个 stream 会累积起来的状态也变成copartiton了。
- 第二个重点是
embeded local state backend。有状态分散式流式处理的引擎,状态可能会累积到非常大,当key非常多时,状态可能就会超出单一节点的memory的负荷量。
这时候状态必须有状态后端(State Backend)去维护它;
在这个状态后端在正常状况下,用in-memory维护即可。
2 Apache Flink 的核心机制
2.0 Streams/数据流
- Streams,流,分为有限数据流与无限数据流
unbounded stream是有始无终的数据流,即无限数据流;bounded stream是限定大小的有始有终的数据集合,即有限数据流- 二者的区别在于无限数据流的数据会随时间的推演而持续增加,计算持续进行且不存在结束的状态,相对的有限数据流数据大小固定,计算最终会完成并处于结束的状态。
Flink 并行数据流 : Stream / Stream Partition | 3类 Operator / Operator之间的数据传递与重新分区
Flink程序在执行的时候,会被映射成一个Streaming Dataflow。
- 一个
Streaming Dataflow是由一组Stream和Transformation Operator组成的。- 在启动时,从一个或多个
Source Operator开始,结束于一个或多个Sink Operator。
Source Operator / Transformation Operator / Sink Operator
Flink程序本质上是并行的和分布式的,在执行过程中,一个流(stream)包含一个或多个流分区(Stream partition)。
- 而每一个
operator包含一个或多个operator子任务(Sub Task)。- 操作子任务间彼此独立,在不同的线程中执行,甚至是在不同的机器或不同的容器上。
operator子任务的数量是这一特定operator的并行度。- 相同程序中的不同
operator有不同级别的并行度。

- 一个
Stream可以被分成多个Stream的分区,也就是Stream Partition。
- 一个
Operator也可以被分为多个Operator Subtask。(如上图中,Source 被分成 Source1 和 Source2,它们分别为 Source 的 Operator Subtask。)- 每一个
Operator Subtask都是在不同的线程当中独立执行的。- 一个
Operator的并行度,就等于Operator Subtask的个数。(上图 Source 的并行度为 2)。- 而一个
Stream的并行度就等于它生成的 Operator 的并行度。
- 数据在2个
operator之间传递的时候有2种模式:
One to One模式:2个 operator 用此模式传递的时候,会保持数据的分区数和数据的排序如上图中的 Source1 到 Map1,它就保留的 Source 的分区特性,以及分区元素处理的有序性。
Redistributing(重新分配)模式:这种模式会改变数据的分区数;每个一个 operator subtask 会根据选择 transformation 把数据发送到不同的目标 subtasks,比如,
keyBy()会通过hashcode重新分区,broadcast()和rebalance()方法会随机重新分区;
2.1 状态容错 : Checkpoint / Savepoint
- 当我们考虑状态容错时难免会想到精确一次的状态容错,应用在运算时累积的状态,每笔输入的事件都反映到状态,更改状态都是精确一次,如果修改超过一次的话也意味着数据引擎产生的结果是不可靠的。
- 如何确保状态拥有精确一次(Exactly-once guarantee)的容错保证?
- 如何在分散式场景下替多个拥有本地状态的运算子产生一个全域一致的快照(Global consistent snapshot)?
- 更重要的是,如何在不中断运算的前提下产生快照?
2.1.1 简单场景的精确一次容错方法
- 还是以使用者出现次数来看,如果某个使用者出现的次数计算不准确,不是精确一次,那么产生的结果是无法作为参考的。
- 在考虑精确的容错保证前,我们先考虑最简单的使用场景,如:
- 无限流的数据进入,后面单一的 Process 进行运算,每处理完一笔计算即会累积一次状态。
这种情况下,如果要确保
Process产生精确一次的状态容错,每处理完一笔数据,更改完状态后进行一次快照,快照包含在队列中并与相应的状态进行对比,完成一致的快照,就能确保精确一次。
2.1.2 分布式状态容错
Flink作为分布式的处理引擎,在分布式的场景下,进行多个本地状态的运算,只产生一个全域一致的快照,如需要在不中断运算值的前提下产生全域一致的快照,就涉及到分散式状态容错。
Global consistent snapshot/全局一致性快照

关于
Global consistent snapshot,当Operator在分布式的环境中,在各个节点做运算。
首先产生Global consistent snapshot的方式就是处理每一笔数据的快照点是连续的,这笔运算流过所有的运算值,更改完所有的运算值后,能够看到每一个运算值的状态与该笔运算的位置,即可称为consistent snapshot,当然,Global consistent snapshot也是简易场景的延伸。
容错恢复

- 首先,了解一下
Checkpoint,上面提到连续性快照,每个Operator运算值本地的状态后端都要维护状态。
也就是每次将产生检查点时会将它们传入共享的
DFS** 中。
当任何一个Process挂掉后,可以直接从三个完整的Checkpoint将所有的运算值的状态恢复,重新设定到相应位置。
Checkpoint的存在使,整个Process能够实现分散式环境中的Exactly-once**。
2.1.3 分散式快照(Distributed Snapshots)方法

关于 Flink 如何在不中断运算的状况下持续产生
Global consistent snapshot,其方式是基于用simple lamport演算法机制下延伸的。
已知的一个点Checkpoint barrier, Flink 在某个Datastream中会一直安插Checkpoint barrier,Checkpoint barrier也会N — 1等等,Checkpoint barrier N代表着所有在这个范围里面的数据都是Checkpoint barrier N。

举例:假设现在需要产生 Checkpoint barrier N,但实际上在 Flink 中是由 job manager 触发 Checkpoint,Checkpoint 被触发后开始从数据源产生 Checkpoint barrier。当 job 开始做 Checkpoint barrier N 的时候,可以理解为 Checkpoint barrier N 需要逐步填充左下角的表格。

如图,当部分事件标为红色,Checkpoint barrier N 也是红色时,代表着这些数据或事件都由 Checkpoint barrier N 负责。Checkpoint barrier N 后面白色部分的数据或事件则不属于 Checkpoint barrier N。
在以上的基础上,当数据源收到 Checkpoint barrier N 之后会先将自己的状态保存,以读取 Kafka 资料为例,数据源的状态就是目前它在 Kafka 分区的位置,这个状态也会写入到上面提到的表格中。下游的 Operator 1 会开始运算属于 Checkpoint barrier N 的数据,当 Checkpoint barrier N 跟着这些数据流动到 Operator 1 之后,Operator 1 也将属于 Checkpoint barrier N 的所有数据都反映在状态中,当收到 Checkpoint barrier N 时也会直接对 Checkpoint 去做快照。

当快照完成后继续往下游走,Operator 2 也会接收到所有数据,然后搜索 Checkpoint barrier N 的数据并直接反映到状态,当状态收到 Checkpoint barrier N 之后也会直接写入到 Checkpoint N 中。以上过程到此可以看到 Checkpoint barrier N 已经完成了一个完整的表格,这个表格叫做 Distributed Snapshots,即分布式快照。分布式快照可以用来做状态容错,任何一个节点挂掉的时候可以在之前的 Checkpoint 中将其恢复。继续以上 Process,当多个 Checkpoint 同时进行,Checkpoint barrier N 已经流到 job manager 2,Flink job manager 可以触发其他的 Checkpoint,比如 Checkpoint N + 1,Checkpoint N + 2 等等也同步进行,利用这种机制,可以在不阻挡运算的状况下持续地产生 Checkpoint。
2.1.4 状态保存与迁移:保存点(SavePoint)——手动产生1个Checkpoint
- 流式处理应用无时无刻不在运行,运维上有几个重要考量:
- 更改应用逻辑 / 修 bug 等,如何将前一执行的状态迁移到新的执行?
- 如何重新定义运行的平行化程度?
- 如何升级运算丛集的版本号?
-
Checkpoint完美符合以上需求,不过Flink中还有另外一个名词:保存点(Savepoint),当手动产生一个Checkpoint的时候,就叫做一个Savepoint。 -
Savepoint跟Checkpoint的差别在于:检查点是 Flink 对于一个有状态应用在运行中利用分布式快照持续周期性的产生Checkpoint,而Savepoint则是人工手动产生的Checkpoint,Savepoint记录着流式应用中所有运算元的状态。

如图,Savepoint A 和 Savepoint B,无论是变更底层代码逻辑、修 bug 或是升级 Flink 版本,重新定义应用、计算的平行化程度等,最先需要做的事情就是产生
Savepoint。
Savepoint产生的原理是在Checkpoint barrier流动到所有的Pipeline中手动插入从而产生分布式快照,这些分布式快照点即Savepoint。
Savepoint可以放在任何位置保存,当完成变更时,可以直接从Savepoint恢复、执行。
- 从
Savepoint的恢复执行时,需要注意,在变更应用的过程中时间在持续,如 Kafka 在持续收集资料,当从 Savepoint 恢复时,Savepoint 保存着 Checkpoint 产生的时间以及 Kafka 的相应位置,因此它需要恢复到最新的数据。
无论是任何运算,Event - Time 都可以确保产生的结果完全一致。
- 假设恢复后的重新运算用
Process Event - Time,将 windows 窗口设为 1 小时,重新运算能够在 10 分钟内将所有的运算结果都包含到单一的 windows 中。
而如果使用 Event – Time,则类似于做 Bucketing。
在 Bucketing 的状况下,无论重新运算的数量多大,最终重新运算的时间以及 windows 产生的结果都一定能保证完全一致。
2.2 状态维护 : StateBackend
- 状态维护,即用一段代码在本地维护状态值,当状态值非常大时需要本地的状态后端来支持。
- 在 Apache Flink 中,状态后端(
StateBackend)是管理状态存储、访问和维护的核心组件。- 状态后端定义了状态的存储方式,以及在发生故障时如何恢复状态。
- Flink 提供了3种类型的状态后端:
MemoryStateBackend、FsStateBackend和RocksDBStateBackend。- 在维护状态时可以根据状态的数量选择相应的状态后端。
如果状态数据量不大,可以使用
FsStateBackend。
对于大规模状态数据的处理,建议使用RocksDBStateBackend。

如图,在 Flink 程序中,可以采用 getRuntimeContext().getState(desc); 这组 API 去注册状态。
Flink 有多种状态后端,采用 API 注册状态后,读取状态时都是通过状态后端来读取的。Flink 有3种不同的状态值,也有3种不同的状态后端:
JVM Heap 状态后端(MemoryStateBackend)

MemoryStateBackend
- 将所有状态数据存储在 JVM 堆内存中,适用于小规模状态数据的场景,如本地测试和调试。
- JVM Heap 状态后端,适合数量较小的状态,当状态量不大时就可以采用 JVM Heap 的状态后端。
但由于内存容量的限制,不推荐在【生产环境】中使用。它是 Flink 默认的状态后端。
- JVM Heap 状态后端会在每一次运算值需要读取状态时,用 Java object read / writes 进行读或写,不会产生较大代价,但当 Checkpoint 需要将每一个运算值的本地状态放入 Distributed Snapshots 的时候,就需要进行序列化了。

RocksDB 状态后端(RocksDBStateBackend)
RocksDBStateBackend:
- 它是一种
out of core的状态后端。- 使用
RocksDB作为本地数据库存储状态数据,并支持增量Checkpoint。- 它适合处理大规模状态数据的场景,如天级窗口聚合。
RocksDBStateBackend 在性能上优于
FsStateBackend,因为它存储了最新的热数据,并通过异步方式同步到文件系统中。
- 在
Runtime的本地状态后端让使用者去读取状态的时候会经过磁盘。相当于将状态维护在磁盘里,与之对应的代价可能就是每次读取状态时,都需要经过序列化和反序列化的过程。
当需要进行快照时只将应用序列化即可,序列化后的数据直接传输到中央的共享 DFS 中。
文件系统状态后端(FsStateBackend)
FsStateBackend基于文件系统,支持本地或分布式文件系统(如 HDFS / OSS)。
- 它适用于状态数据较大的场景,如长时间窗口的计算或大量 Key/Value 状态数据。
FsStateBackend通过异步方式将状态数据持久化到文件系统中,提高了状态数据的安全性。
配置状态后端
Flink默认使用MemoryStateBackend,无需显式配置。
- 对于其他状态后端,需要在应用级别或集群级别进行配置。
- 应用级别的配置通过
StreamExecutionEnvironment设置:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new FsStateBackend("hdfs://namenode:40010/flink/checkpoints"));
如果使用
RocksDBStateBackend,则需要引入相关依赖,并进行类似的配置。
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
// 'true' 是启用增量检查点
env.setStateBackend(new RocksDBStateBackend("hdfs:///flink-checkpoints", true));
RocksDB 的优化和最佳实践
- 1 定时器优化:将定时器放到堆内存中,以提高访问效率。
- 2 磁盘性能:将
state.backend.rocksdb.localdir目录配置到本地磁盘上,以提高 RocksDB 的性能。- 3 后台刷新和压缩的并行性:在多核 CPU 机器上,通过设置
state.backend.rocksdb.thread.num来增加后台刷新和压缩的并行性。
- 集群级别的配置通过
flink-conf.yaml文件进行,可以设置状态后端类型和Checkpoint存储路径。例如:
state.backend: rocksdb
state.backend.incremental: true
state.checkpoints.dir: hdfs:///flink-checkpoints
- 在生产环境中,应根据状态数据的大小和应用场景选择合适的状态后端,以确保性能和数据的安全性。
2.3 Event - Time
2.3.1 不同时间种类: Event Time <== Process Time
- 在 Flink 及其他进阶的流式处理引擎出现之前,大数据处理引擎一直只支持
Processing-time的处理。
假设定义一个运算
windows的窗口,windows运算设定每小时进行结算。
以Processing-time进行运算时可以发现数据引擎将 3 点至 4 点间收到的数据做结算。
实际上在做报表或者分析结果时是想了解真实世界中 3 点至 4 点之间实际产生数据的输出结果,了解实际数据的输出结果就必须采用Event – Time了。

如图,
Event - Time相当于事件,它在数据最源头产生时带有时间戳,后面都需要用时间戳来进行运算。
用图来表示,最开始的队列收到数据,每小时对数据划分一个批次,这就是Event - Time Process在做的事情。
2.3.2 Event-Time 处理

Event-Time是用事件真实产生的时间戳去做Re-bucketing,把对应时间 3 点到 4 点的数据放在 3 点到 4 点的Bucket,然后Bucket产生结果。
所以,
Event - Time跟Processing - time的概念是这样对比的存在。

Event - Time的重要性在于记录引擎输出运算结果的时间。
简单来说,流式引擎连续 24 小时在运行、搜集资料,假设
Pipeline里有一个windows Operator正在做运算,每小时能产生结果,何时输出 windows 的运算值,这个时间点就是Event - Time处理的精髓,用来表示该收的数据已经收到。
2.3.3 Watermarks

Flink实际上是用watermarks来实现Event - Time的功能。
Watermarks在 Flink 中也属于特殊事件,其精髓在于当某个运算值收到带有时间戳“T”的watermarks时,就意味着它不会接收到新的数据了。- 使用 watermarks 的好处在于:可以准确预估收到数据的截止时间。
- 举例,假设预期收到数据时间与输出结果时间的时间差延迟 5 分钟,那么 Flink 中所有的 windows Operator 搜索 3 点至 4 点的数据,但因为存在延迟需要再多等 5 分钟直至收集完 4:05 分的数据,此时方能判定 4 点钟的资料收集完成了,然后才会产出 3 点至 4 点的数据结果。这个时间段的结果对应的就是 watermarks 的部分。
Z 概念补充
作业(Job)
类似于MaxCompute或Hadoop Job,一个实时计算的作业描述了一个完整的流式数据处理业务逻辑,是流式计算的基础业务单元。

UDF (User Define Function)
- 实时计算 Flink支持UDF函数。类似于Hive UDF函数,Flink SQL提供了标准化的流式数据处理能力同时,对于部分业务特殊自定义处理逻辑,建议您使用UDF函数表达。
- 目前实时计算 Flink仅支持Java的UDF函数扩展。
资源 (Resource)
当前UDF函数仅支持使用Java语言表达,对于您上传的每个JAR,实时计算定义为一个Resource。
DataOps by Flink
数据采集 (Data Collection)
- 广义的数据采集指,将数据从数据产生方收集并传输进入到大数据处理引擎的过程。
- 在实时计算 Flink,数据采集原则上遵循上述定义,但更加聚焦为将流式数据从数据产生方收集并传输进入数据总线的过程。
数据存储 (Data Storage)
- 实时计算 Flink 定义为一种轻量级计算引擎,本身不带有任何业务数据存储系统。
- 实时计算 Flink 均是使用外部数据存储作为数据来源和数据目的端进行使用。
- 实时计算将数据存储均定义为外部的数据存储。
例如,将您RDS作为结果表,那么RDS即是实时计算的一类DataStore。
- 支持流式输入表类型包括
- 大数据总线(DataHub)
- 日志服务(LogService)
- 消息服务(MQ)
- 支持静态输入表类型包括
- 表格存储(TableStore)
- 云数据库(RDS)
- 支持输出表类型包括
- 大数据总线(DataHub)
- 日志服务(LogService)
- 表格存储(TableStore)
- 云数据库(RDS)
- 消息服务(MQ)
数据加工 (Data Development)
- 流式计算的开发过程(编写Flink SQL Job、Flink API Job的过程)定义为数据加工。
- 实时计算 Flink可提供一整套包括开发、调试的在线IDE,服务流式数据加工过程。
数据运维 (Data Operation)
- 实时计算作业的在线运维定义为数据运维。
- 实时计算 Flink可提供一整套管控平台,方便您进行流式数据的运维管控。
资源估算类
Compute Unit/CU/实时计算单元 := 1 CPU / 4GB
- Flink工作空间的基本计量单位为Compute Unit(CU)
- 即: 计算资源,1 CU=1核CPU+4 GiB内存+20
- 华为云(DLI)、阿里云(实时计算Flink版),对1CU的定义,均是 1Cpu 、4GB
- 在实时计算 Flink中,作业的实时计算单元为CU。一个CU描述了一个实时计算作业最小运行能力,即在限定的CPU、内存、I/O情况下对于事件流处理的能力。一个实时计算作业可以指定在一个或者多个CU上运行。
当前实时计算定义,1CU的处理能力大概为 1000条数据 / 秒。 (阿里云)
- GB本地存储(放置日志、系统检查点等信息),CU对应实时计算底层系统的CPU计算能力。
1个实时计算作业(Job)的CU使用量取决于此Job输入数据流的QPS、计算复杂程度,以及具体的输入数据分布情况。
您可以根据业务规模以及实时计算的计算能力,估算所需购买的资源数量。
实时计算1 CU的处理能力如下表所示。
| 处理场景 | 处理能力 |
|---|---|
| 简单的流式压测处理 例如,过滤、清洗等操作。 |
1 CU每秒可以处理40000~55000条数据。 |
| 复杂的流式压测处理 例如,聚合操作、复杂UDF计算等。 |
1 CU每秒可以处理5000~10000条数据。 |
- 计算能力的特别说明
- 上述计算能力估值仅限于实时计算内部处理能力,不包括对外数据读取和写入部分。
- 外部数据的读写效率会影响您对实时计算能力的评估。
例如:如果实时计算需要从日志服务(LogService)读取数据,但LogService对于请求调用配额(Quota)存在一定限制,则:实时计算整体的计算能力将被限制在LogService允许的范围内。
- 如果实时计算引用的RDS数据存储存在连接数或者TPS限制,则:实时计算Job的吞吐能力将受限于RDS本身的流控限制。
- 如果作业中使用窗口函数,CU的使用量会比简单作业高,建议至少购买 4 CU。
Slot/任务槽
taskslot是静态概念 , 是指 单 TaskManager 具有的并发执行能力- 可以通过参数
taskmanager.numberOfTaskSlots进行配置
并行度/parallelism
-
并行度是动态概念,也就是 TaskManager 运行程序时实际使用的并发能力,可以通过参数 parallelism.default 进行配置
-
如果并行度 <= 集群中可用 slot 的总数,则:Flink Job 可正常执行
因为 slot 不一定要全部占用,有十分力气可以只用八分;
- 而如果并行度 > 可用 集群中 slot 的总数,则: 导致超出了并行能力上限————心有余力不足,Flink Job 就只好等待资源管理器分配更多的资源了。
最佳实践:华为云 DLI Flink Job 资源估算的核心概念
- 【单TM所占CU数】假定值
n= 1
华为云DLI给Flink Job实际分配CPU时,会虚拟CPU给 Flink Job 的 Task Manager。
这个虚拟的比例是2n + 1
真实的资源根据DLI Job配置界面中配置的【单TM Slot数】的资源来计算

假定【单TM所占CU数】为1,则:Flink Web UI中 Task Manager 的 CPU 数会显示为 3
- 【CU数量】 = 作业占用资源总CU数,需配置与实际占用资源一致,作业实际占用资源根据算子并行数按需申请。
CU数量 = 管理单元 + (算子总并行数 / 单TM Slot数) * 单TM所占CU数
即: TaskManager 数量 = 算子总并行数 / 单TM Slot数TaskManager 数量,是按需的、动态变化的
-
【实际CU数量】= 弹性资源池当前分配的CU
-
【管理单元】 = Job Manager
-
【并行数】
算子默认最大并行数,优先级比代码中低。
并行数为作业每个算子的并行数,适度增加并行数会提高作业整体算力,但也须考虑线程增多带来的切换开销,其上限是计算单元CU数的4倍;
最佳实践为计算单元CU数的1-2倍。
注意:该并行数设置优先级低于代码中并行数设置。
==============
Slot(任务槽)是Flink中用于执行任务的资源单位,每个TaskManager可以提供多个Slot,每个Slot可以运行一个并行任务。
并行度(parallelism)是指作业执行的并行程度,即:作业中所有并行子任务的总数。
Slot和并行度的关系:
需 TaskManager 的Slot数 * TaskManager 的数量 >= 作业的并行度;否则,作业无法充分利用所有资源。
若 作业的并行度 > 集群中所有TaskManager提供的Slot总数,则:作业将无法启动更多的并行任务,即使资源足够。
参考文献
关于 CU 的说明
关于 CU 的说明
- 什么是阿里云实时计算Flink版 - Aliyun
- 实时计算 Flink版-产品概述-产品简介-应用场景 - Aliyun
- 实时计算Flink——基本概念 - Aliyun
- [Flink] Flink Job之Web UI - 博客园/千千寰宇
- Flink 1.17教程:任务槽Task Slots和并行度的关系 - 51CTO 【推荐】
Y 推荐文献
- Apache Flink
- Flink CDC
X 参考文献
本文链接: https://www.cnblogs.com/johnnyzen
关于博文:评论和私信会在第一时间回复,或直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
日常交流:大数据与软件开发-QQ交流群: 774386015 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!

浙公网安备 33010602011771号