知其然,知其所以然,通过之前的文章,我们已然了解了如何编写一个Flink程序并提交运行,那么这个程序是如何在集群中运行的呢?今天,就给大家分享下Flink的运行时架构。
观察上图,可以看到,Flink作业在执行过程中会涉及到作业管理、资源管理、任务管理、分发器等部分。我们来逐个分析:
作业管理器是控制应用程序的主进程。
通常涉及以下结构:
对Client提交作业的处理流程:
JobGraph转换成一个物理层面的的数据流图,即执行图(ExecutionGraph),包含了所有可并发执行的任务
JobManager向资源管理器(ResourceManager)请求执行任务必要的资源,即TaskManager上的slot
获取足够资源后,将执行图分发到真正运行它们的TaskManager
运行过程中,JobManager负责所有需要中央协调的操作,如检查点(checkpoint)的协调
资源管理器主要负责管理任务管理器(TaskManager)的插槽(slot),TaskManager slot是Flink中定义的处理资源单元。
不同环境和资源管理工具提供了不同的资源管理器,在国内通常以Yarn作为Flink集群的资源管理器。
流程:
JobManager向ResourceManager申请插槽资源,ResourceManager将有空闲插槽的TaskManager分配给JobManager
如果ResourceManager没有足够的插槽来满足JobManager请求,还可以向资源提供平台发起会话,以提供启动TaskManager进程的容器
ResourceManager还负责终止空闲的TaskManager,释放计算资源
任务管理器是Flink中的真正进行工作的进程。
Flink中会有多个TaskManager运行,每一个TaskManager都包含一定数量的插槽
插槽的数量限制了TaskManager能够执行的任务数量
流程:
集群启动之后,TaskManager向资源管理器注册它的插槽
收到资源管理器指令后,TaskManager会将一个或多个插槽提供给JobManager使用
JobManager就可以向插槽分配任务进行执行
执行过程中,一个TaskManager可以同其它运行同一程序的TaskManager交换数据
分发器主要作用在当一个应用被提交执行时,分发器就会启动并将应用移交给一个JobManager,可以跨作业运行,为应用提交提供了REST接口。
在01中,我们介绍了各个组件所承担的职责,那么在一个完整的作业提交过程中,它们是怎么协同配合完成作业提交的呢?
可以看到,作业提交大致分为五个步骤:
接着,我们以常用的yarn为例,看看在yarn中Flink任务是如何提交运行的。
到此为止,相信你对Flink运行时的架构和作业运行流程有了清晰的认识,下面让我们再来看看其内部涉及到的一些原理。
首先,对运行时架构图做一下概念上的补充:
Client
JobManager
TaskManager
下面,让我来给你介绍下Flink作业在生产中的一些问题。
Flink中每一个TaskManager都是一个JVM进程,它可能会在独立的线程上执行一个或多个subtask。
下图中,每个TaskManager拥有3个slot。
子任务的算子可以共享slot,下图中,Source[1]、map[1]、keyBy().window().apply()[1]、sink[1]等子任务共享了第一个slot。
Flink程序由三部分组成:Source、Transformation、Sink
运行时,Flink上运行的程序会被映射成“逻辑数据流”(dataflows),每一个dataflow以一个或多个sources开始,以一个或多个sinks结束。
dataflow类似于任意的有向无环图(DAG),程序中的转换运算(Transformations)跟dataflow中的算子(operator)可能是一对一或一对多的关系
Flink程序直接映射成的数据流图是StreamGraph,即逻辑视图,表示的是计算逻辑的高级视图。
为了执行一个流处理程序,Flink需要将逻辑流图转换为物理数据流图(也叫执行图),执行图详细说明了程序的执行方式。
Flink的执行图可以分为四层:StreamGraph——》JobGraph——》ExecutionGraph——》物理执行图
下图详细展示了Flink执行图的衍生过程。
Flink程序具有并行、分布式的特性。
执行过程中,一个流包含一个或多个分区,每一个算子可以包含一个或多个子任务,这些子任务在不同的线程、不同的物理机或不同的容器中彼此互不依赖的执行。
一个特定算子的子任务的个数被称之为并行度,一般把所有算子中最大的并行度称之为一个流程序的并行度;一个程序中,不同的算子可能具有不同的并行度。
如下图中,sink算子的并行度为1,map算子的并行度为2,整个程序的并行度为2。
Stream在算子之间传输数据的形式可以是one-to-one(forwarding)的模式,也可以是redistributing的模式。
在上文Flink执行图转换过程中,我们提到,从StreamGraph到JobGraph的过程中,会将符合条件的节点chain到一起,从而优化处理流程,这就是Operator Chains。
相同并行度的one-to-one操作,相连的算子链接在一起形成一个task,原来的算子成为里面的一部分
将算子链接成task是非常有效的优化:
下图中,key Agg算子和Sink算子被链接到了一起:
Flink的作业失败大体可以分为两种原因:集群Master失败和Task执行失败。
Flink 支持启动多个 Master 作为备份,这些 Master 可以通过 ZK 来进行选主,从而保证某一时刻只有一个Master 在运行。当前active的 Master 发生异常时 , 某个备份的 Master 可以接管协调的工作。
为了保证Master 可以准确维护作业的状态,Flink 目前采用了一种最简单的实现方式,即直接重启整个作业。
对于Task执行失败的情况,我们需要逐个分析:
Restart-all
粗暴的直接重启所有Task,即从上一次的Checkpoint开始继续执行Task。
Restart-individual
对于各个Task之间不存在数据传输的情况,我们可以直接重启出错的Task。
Restart-Based
Flink 引入了一种新的 Region-Based 的 Failover 策略,用于处理批处理作业中没有Checkpoint导致的失败重启问题。
Flink 的批处理作业中,Task 之间有两种数据传输方式
Flink 将 ExecutionGraph 中使用 Pipeline 方式传输数据的 Task 的子图叫做 Region,从而将整个ExecutionGraph 划分为多个子图。Region 内的 Task 必须同时重启,而不同 Region 的 Task 由于在 Region 边界存在 Blocking 的边,可以单独重启下游 Region 中的 Task