Flink
的核心计算架构是下图中的 Flink Runtime
执行引擎,它是一个分布式系统,能够接受数据流程序并在一台或多台机器上以容错方式执行。
Flink Runtime
执行引擎可以作为 YARN(Yet Another Resource Negotiator)
的应用程序在集群上运行,也可以在 Mesos 集群
上运行,还可以在单机上运行(这对于调试 Flink
应用程序来说非常有用)。
上图为 Flink
技术栈的核心组成部分,值得一提的是,Flink
分别提供了面向流式处理的接口(DataStream API
)和面向批处理的接口(DataSet API
)。因此,Flink
既可以完成流处理,也可以完成批处理。 Flink
支持的拓展库涉及机器学习(FlinkML
)、复杂事件处理(CEP
)、以及图计算(Gelly
),还有分别针对流处
理和批处理的 Table API
。
Flink
提 供 了 用 于 流 处 理 的DataStream API
和用于批处理的 DataSet API
。值得注意的是,尽管 Flink Runtime
执行引擎是基于流处理的,但是 DataSet API
先于 DataStream API
被开发出来,这是因为工业界对无限流处理的需求在 Flink
诞生之初并不大。
DataStream API
可以流畅地分析无限数据流,并且可以用 Java 或者 Scala 来实现。开发人员需要基于一个叫 DataStream
的数据结构来开发,这个数据结构用于表示永不停止的分布式数据流。
Flink
的分布式特点体现在它能够在成百上千台机器上运行,它将大型的计算任务分成许多小的部分,每个机器执行一部分。Flink 能够自动地确保发生机器故障或者其他错误时计算能够持续进行,或者在修复 bug 或进行版本升级后有计划地再执行一次。这种能力使得开发人员不需要担心运行失败。Flink
本质上使用容错性数据流
,这使得开发人员可以分析持续生成且永远不结束的数据(即流处理)。
JobManager 与 TaskManager
Flink
运行时包含了两种类型的处理器:
JobManager 处理器:也称之为 Master
,用于协调分布式执行,它们用来调度 task
,协调检查点,协调失败时恢复等。Flink
运行时至少存在一个 master
处理器,如果配置高可用模式则会存在多个 master
处理器,它们其中有一个是 leader
,而其他的都
是 standby
。
TaskManager 处理器:也称之为 Worker
,用于执行一个 dataflow
的 task(或者特殊的 subtask)、数据缓冲和 data stream
的交换,Flink
运行时至少会存在一个 worker
处理器。
Master
和 Worker
处理器可以直接在物理机上启动,或者通过像 YARN
这样的资源调度框架。
Worker
连接到 Master
,告知自身的可用性进而获得任务分配。
任务提交流程
Flink
任务提交后,Client
向 HDFS
上传 Flink
的 Jar
包和配置,之后向 Yarn ResourceManager
提交任务,ResourceManager
分配 Container
资源并通知对应的NodeManager
启动 ApplicationMaster
,ApplicationMaster
启动后加载 Flink
的 Jar
包和配置构建环境,然后启动 JobManager
,之后 ApplicationMaster
向 ResourceManager
申请资源启动 TaskManager
, ResourceManager
分 配 Container
资 源 后 , 由ApplicationMaster
通 知 资源所在节点的 NodeManager
启 动 TaskManager
,NodeManager
加载 Flink
的 Jar
包和配置构建环境并启动 TaskManager
,TaskManager
启动后向 JobManager
发送心跳包,并等待 JobManager
向其分配任务。
TaskManager 与 Slots
每一个 TaskManager
是一个 JVM
进程,它可能会在独立的线程上执行一个或多个 subtask
。为了控制一个 worker
能接收多少个 task
,worker
通过 task slot
来进行控制(一个 worker
至少有一个 task slot
)。·每个 task slot
表示 TaskManager
拥有资源的一个固定大小的子集。假如一个TaskManager
有三个 slot
,那么它会将其管理的内存分成三份给各个 slot
。资源 slot化
意味着一个 subtask
将不需要跟来自其他 job
的 subtask
竞争被管理的内存,取而代之的是它将拥有一定数量的内存储备。需要注意的是,这里不会涉及到 CPU 的隔
离,slot
目前仅仅用来隔离 task
的受管理的内存。
通过调整 task slot
的数量,允许用户定义 subtask
之间如何互相隔离。如果一个 TaskManager
一个 slot,那将意味着每个 task group
运行在独立的 JVM
中(该 JVM可能是通过一个特定的容器启动的),而一个 TaskManager
多个 slot
意味着更多的subtask
可以共享同一个 JVM
。而在同一个 JVM
进程中的 task
将共享 TCP 连接
(基
于多路复用)和心跳消息。它们也可能共享数据集和数据结构,因此这减少了每个task
的负载。
Task Slot
是静态的概念,是指 TaskManager
具有的并发执行能力,可以通过参数 taskmanager.numberOfTaskSlots
进行配置,而并行度 parallelism
是动态概念,即 TaskManager
运行程序时实际使用的并发能力,可以通过参数 parallelism.default
进行配置。
也就是说,假设一共有 3 个 TaskManager
,每一个 TaskManager
中的分配 3 个TaskSlot
,也就是每个 TaskManager
可以接收 3 个 task
,一共 9 个 TaskSlot
,如果我们设置 parallelism.default=1
,即运行程序默认的并行度为 1,9 个 TaskSlot
只用了 1个,有 8 个空闲,因此,设置合适的并行度才能提高效率。
Dataflow
Flink
程序由 Source、Transformation、Sink
这三个核心组件组成,Source
主要负责数据的读取,Transformation
主要负责对属于的转换操作,Sink
负责最终数据的输出,在各个组件之间流转的数据称为流(streams
)
Flink
程序的基础构建模块是 流(streams
) 与 转(transformations
)(需要注意的是,Flink
的 DataSet API
所使用的 DataSets
其内部也是 stream
)。一个 stream
可以看成一个中间结果,而一transformations
是以一个或多个 stream
作为输入的
某种 operation
,该 operation
利用这些 stream
进行计算从而产生一个或多个 result stream
。
在运行时,Flink
上运行的程序会被映射成 streaming dataflows
,它包含了streams
和 transformations operators
。每一个 dataflow
以一个或多个 sources
开始以一个或多个 sinks
结束,dataflow
类似于任意的有向无环图(DAG
)。
并行数据流
Flink
程序的执行具有并行、分布式
的特性。在执行过程中,一个 stream
包含一个或多个 stream partition
, 而 每 一 个 operator
包 含 一 个 或 多 个 operator subtask
,这些 operator subtasks
在不同的线程、不同的物理机或不同的容器中彼此互不依赖得执行。
一个特定 operator
的 subtask
的个数被称之为其 parallelism
(并行度)。一个stream
的并行度总是等同于其 producing operator
的并行度。一个程序中,不同的operator
可能具有不同的并行度。
Stream
在 operator
之间传输数据的形式可以是 one-to one(forwarding)
的模式也可以是 redistributing
的模式,具体是哪一种形式,取决于 operator
的种类。
One-to-one:stream(比如在 source 和 map operator 之间)维护着分区以及元素的顺序。那意味着 map operator
的 subtask 看到的元素的个数以及顺序跟 sourceoperator 的 subtask 生产的元素的个数、顺序相同,map、fliter、flatMap 等算子都是one-to-one 的对应关系。
Redistributing:这种操作会改变数据的分区个数。每一个 operator subtask
依据所 选 择 的 transformation
发 送 数 据 到 不 同 的 目 标 subtask。 例 如 , keyBy()
基 于hashCode 重分区、broadcast 和 rebalance 会随机重新分区,这些算子都会引起redistribute
过程,而 redistribute
过程就类似于 Spark
中的 shuffle
过程
task 与 operator chains
出于分布式执行的目的,Flink
将 operator
的 subtask
链接在一起形成 task
,每个 task
在一个线程中执行。将 operators
链接成 task
是非常有效的优化:它能减少线程之间的切换和基于缓存区的数据交换,在减少时延的同时提升吞吐量。链接的行为可以在编程 API 中进行指定。
下面这幅图,展示了 5 个 subtask
以 5 个并行的线程来执行:
任务调度流程
客户端不是运行时和程序执行的一部分,但它用于准备并发送 dataflow
给Master,然后,客户端断开连接或者维持连接以等待接收计算结果,客户端可以以两种方式运行:要么作为 Java/Scala 程序的一部分被程序触发执行,要么以命令行./bin/flink run
的方式执行。