【基础】Flink -- Runtime Architecture

Flink -- Runtime Architecture

  • 系统架构
    • 整体构成
    • 作业管理器 JobManager
    • 任务管理器 TaskManager
  • 一些重要概念
    • 数据流图 Dataflow Graph
    • 并行度 Parallelism
    • 算子链 Operator Chain
    • 作业图 JobGraph 与执行图 ExecutionGraph
    • 任务 Tasks 和任务槽 Task Slots

系统架构

Flink 利用现有的集群架构和服务,可以配置为独立的集群运行,也可以跟一些集群资源管理工具集成使用,如 YARN、K8S 等;

Flink 持久化的分布式存储直接利用了分布式文件系统 HDFS 或对象存储 S3;

Flink 的靠可用则是通过 ZooKeeper 实现的;

整体构成

Flink 运行时架构最核心的就是两大组件,即作业管理器 JobManager 和任务管理器 TaskManager。对于一个提交的作业,JobManager 是真正的管理者,负责管理调度,在不考虑高可用的情况下一般只有一个;TaskManager 负责执行数据的处理,因此可以有一个或者多个。

Flink 作业提交与任务处理时的系统如下:

【基础】Flink -- Runtime Architecture_第1张图片

作业管理器 JobManager

JobManager 是一个 Flink 集群中任务管理和调度的核心,是控制应用程序的主进程。其又包含三个不同的组件:

JobMaster

JobMaster 是 JobManager 中最核心的组件,与具体的 Job 一一对应,负责处理单独的 Job。作业提交时,JobMaster 会首先接受到要执行的应用,应用一般由客户端提交,包括 jar 包,数据流图 dataflow graph 以及作业图 JobGraph。

JobMaster 会将作业图转换为执行图 ExecutionGraph,同时向资源管理器发出请求,申请执行任务所必须的资源。当获取到足够的资源后,就会将执行图分发到 TaskManager 执行。

而在运行过程中,JobMaster 会负责所有需要中央协调的操作,如检查点的协调。

ResourceManager

ResourceManager 是资源管理器,负责资源的分配以及管理。所谓的资源指的是 TaskManager 中的任务槽 task slots。任务槽是 Flink 中的资源调配单元,包含了机器用来执行计算的一组 CPU 和内存资源。每一个任务都必须分配到一个任务槽上执行。

ResourceManager 针对不同用的环境有不同的实现。独立集群部署时,ResourceManager 只能分发可用 TaskManager 的任务槽;而存在资源管理平台时,若任务槽不足,ResourceManager 还可以向资源提供平台发起会话,请求启动 TaskManager 的容器。

此外,ResourceManager 还负责停止空闲的 TaskManager,释放计算资源。

Dispatcher

Dispatcher 即分发器,其提供一个 REST 接口用于提交应用,并为每一个新提交的作业启动一个新的 JobMaster。

任务管理器 TaskManager

TaskManager 是 Flink 中的工作进程,负责处理具体的业务计算逻辑。每一个 Flink 集群都至少包含一个 TaskManager,每一个 TaskManager 都包含了一定数量的任务槽,任务槽的数量限制了 TaskManager 能够并行处理的任务数量。

在启动后,TaskManager 会向资源管理器注册其任务槽;当收到资源管理器的调度指令后,TaskManager 会将一个或者多个槽位提供给 JobMaster 使用。

一些重要概念

数据流图 Dataflow Graph

Flink 程序在运行时会被映射为所有算子按照逻辑的顺序连接在一起的图,即数据流图。在数据流图中,可以很清楚的看到源算子 Source、转换算子 Transformation 以及下沉算子 Sink。

【基础】Flink -- Runtime Architecture_第2张图片

一般情况下,数据流图中的算子和程序中的转换运算是一一对应的。但并不是基于 DataStream API 的每一个方法调用都是数据流图中的一个算子。数据流图中的一个算子必须是一个转换处理的操作(针对于转换算子),而代码中的方法调用并不一定实现了数据转换,有可能好几个方法一起完成一次数据转换,因此在数据流图中会看到多个方法合并在一个算子中的情况。

并行度 Parallelism

数据的并行是指多条数据同时到来时,程序可以同时读入,并同时在不同的节点进行后续算子操作。Flink 数据的并行是通过“复制”算子操作到多个节点实现的,当数据到来之后将被分配到任意一个节点上执行。

通过上述方式,一个算子任务就被分成了多个并行的子任务 subtask,这些子任务在不同的线程、不同的物理机或者不同的容器中完全独立的执行。某个算子子任务的个数称为该算子的并行度。一般情况下,一个 Flink 流程序的并行度为其所有算子中最大的并行度。如下图所示,整个程序包含 7 个子任务,并行度为 2。

【基础】Flink -- Runtime Architecture_第3张图片

在一个程序中,不用的算子可能具有不同的并行度。并行度可以通过下述几种方法进行设置:

  • 代码设置:在代码中可以在算子之后调用setParallelism()方法,来设置当前算子的并行度;此外,也可以调用环境的setParallelism()方法来全局设置并行度;

  • 提交应用时设置:在使用flink run命令提交应用时,可以增加-p参数来指定当前应用的全局并行度;

  • 配置文件中设置:可以在集群配置文件 flink-conf.yaml 中更改parallelism.default: 2指定并行度,该设置对整个集群上的作业有效,默认为 1;

针对上述的并行度设置方法,他们的优先级如下:

  1. 若代码中单独指定了某算子的并行度,其享有最高优先级;

  2. 在不存在 1 所述情况时,当代码中对执行环境设置了全局并行度,则其享有最高优先级;

  3. 若不存在 1、2 所述情况时,当提交任务时指定了-p参数,则其享有最高优先级;

  4. 若不存在 1、2、3 所述情况时,则采用集群配置文件中的默认并行度;

在实践中,推荐针对算子设置并行度,方便提交作业时进行动态扩展。

算子链 Operator Chain

在并行度小节展示的图片中,可以观察到一个数据流在算子见传输的形式有两种,可以是一对一的形式,也可以是重分区的模式。这种模式取决于算子的种类。

一对一模式(One-to-one, forwarding)

该模式下,两个算子间的数据传递不需要重新分区,数据流维护者分区以及元素的顺序。map、flatMap、filter 等算子都属于一对一的关系。

重分区模式(Redistributing)

该模式下数据由上游算子到下游算子的传递会根据数据传输策略进行重分区。如 keyBy 进行的分组操作。

算子链的合并

在 Flink 中,并行度相同且满足一对一模式的算子操作,可以直接链接在一起形成一个大的任务,链接后的“大”任务会被一个线程执行,因此可以减少线程间的切换和基于缓存区的数据交换,在减少时延的同时提高吞吐量。

在实现算子链的合并后,数据流图最终会存在 5 个任务,由 5 个线程并行执行,如下图所示:

【基础】Flink -- Runtime Architecture_第4张图片

Flink 默认会按照算子链合并的原则进行合并,我们也可以在代码中禁止合并或者自定义合并,如下所示:

// 禁用算子链
.map(word -> Tuple2.of(word, 1L)).disableChaining();
// 从当前算子开始新链
.map(word -> Tuple2.of(word, 1L)).startNewChain();

作业图 JobGraph 与执行图 ExecutionGraph

我们所编写的代码会经过几个阶段的转换,生成不同层级的图,最终将程序直接映射生成的数据流图(也称逻辑流图)转换成物理图,分发给 TaskManager 执行。

Flink 中任务调度执行的图,按照生成顺序可以分成四层:逻辑流图(StreamGraph)→ 作业图(JobGraph)→ 执行图(ExecutionGraph)→ 物理图(Physical Graph)

  1. 逻辑流图(StreamGraph):按照程序中的 DataStream API 直接生成,这一步一般在客户端中执行;

  2. 作业图(JobGraph):执行算子链的合并优化,该步骤同样在客户端执行,在作业提交时将作业图传递给 JobMaster;

  3. 执行图(ExecutionGraph):JobMaster 按照并行度对并行子任务进行拆分,并明确任务间数据传输的方式,生成执行图并分发给 TaskManager。这是调度层最核心的数据结构;

  4. 物理图(Physical Graph):TaskManager 进一步确认数据存放的位置和收发的具体方式,形成物理图并以此进行数据的处理计算;

四层调度的演变过程如下:

【基础】Flink -- Runtime Architecture_第5张图片

任务 Tasks 和任务槽 Task Slots

任务槽 Slots

任务槽表示了 TaskManager 拥有计算资源的一个固定大小的子集,是一组独立的资源。假设一个 TaskManager 有三个 slot,那么它会将管理的内存平均分为三份,每个 slot 独占一份。

任务槽的数量可以通过集群配置文件中的taskmanager.numberOfTaskSlots: 8来设置。通过调整 slot 的数量,可以控制子任务之间的隔离级别。

若 TaskManager 的任务槽只有一个,则每个任务都会完全独立的运行在 JVM 当中;若蛇者多个 slot,则多任务可以共享 JVM。

任务对于任务槽的共享

同属于一个作业的不同任务节点的并行子任务可以共享任务槽。假设现在有 2 个 TaskManager,每个 TaskManager 有 3 个任务槽,则对于前文算子链中的五个任务来说,可以按照下述情况划分任务槽:

【基础】Flink -- Runtime Architecture_第6张图片

若保持 sink 的并行度为 1,将作业提交时的全局并行度设置为 6,此时则总共有 13 个任务,通过共享任务槽的方式,可以如下分配:即每个任务节点的并行子任务一字排开,占据不同的 slot;而不同的任务节点的子任务可以共享 slot。如下图所示:

【基础】Flink -- Runtime Architecture_第7张图片

Flink 默认是允许 slot 共享的,如果希望某个算子对应的任务完全独占一个 slot,或者只有某一部分算子共享 slot,我们也可以通过设置“slot 共享组”(SlotSharingGroup)手动指定:

.map(word -> Tuple2.of(word, 1L)).slotSharingGroup(1);

这样,只有属于同一个 slot 共享组的子任务,才会开启 slot 共享;不同组之间的任务是完全隔离的,必须分配到不同的 slot 上。在这种场景下,总共需要的 slot 数量,就是各个 slot 共享组最大并行度的总和。

任务槽与并行度的关系

整个流处理程序的并行度,就应该是所有算子并行度中最大的那个,这代表了运行程序需要的 slot 数量。
务,才会开启 slot 共享;不同组之间的任务是完全隔离的,必须分配到不同的 slot 上。在这种场景下,总共需要的 slot 数量,就是各个 slot 共享组最大并行度的总和。

你可能感兴趣的:(Flink,flink,大数据,java)