Flink Runtime 核心机制

文章目录

  • 1. 整体架构
    • 1.1 Flink 的整体架构图。
    • 1.2 Flink Runtime 层架构与做业务流程
    • 1.3 Job 运行模式
  • 2. 资源管理与任务调度
    • 2.1 Slot 的管理和分配
      • 2.1.1 Task 启动时,Slot 的调度分配流程
      • 2.1.2 Task 执行结束后,Slot 的释放流程
      • 2.1.3 心跳
    • 2.2 Slot 描述描述
    • 2.3 Slot Sharing
    • 2.4 Job 的调度与 DAG 执行图
    • 2.5 调度策略
  • 3. 错误恢复
    • 3.1 Task Failover
      • 3.1.1 Restart-all
      • 3.1.2 Restart-individual
      • 3.1.3 Restart-Region
    • 3.2 Master Failover
  • 4. 未来展望

1. 整体架构

1.1 Flink 的整体架构图。

Flink 可以通过单进程多线程的方式直接运行,从而提供调试的能力。也可以运行在 Yarn 或者 K8S 这种资源管理系统上面,也可以在像 EC2 这种云环境中执行。
Flink Runtime 核心机制_第1张图片
针对不同的执行环境,Flink 提供了一套统一的分布式作业执行引擎 Flink Runtime 。Flink 在 Runtime 层之上提供了 DataStream 和 DataSet 两套 API,分别用来编写流作业与批作业,以及一组更高级的 Table API 、SQL、CEP 来简化特定作业的编写。

1.2 Flink Runtime 层架构与做业务流程

Flink Runtime 层的整个架构主要是在 FLIP-6 中实现的,整体来说,它采用了标准 master-slave 的结构,其中左侧白色圈中的部分即是 master,它负责管理整个集群中的资源和作业;而右侧的两个 TaskExecutor 则是 Slave,负责提供具体的资源并实际执行作业。
Flink Runtime 核心机制_第2张图片
其中,Master 部分又包含了三个组件,即 Dispatcher、ResourceManager 和 JobManager。其中,Dispatcher 负责接收用户提供的作业,并且负责为这个新提交的作业拉起一个新的 JobManager 组件。ResourceManager 负责资源的管理,在整个 Flink 集群中只有一个 ResourceManager。JobManager 负责管理作业的执行,在一个 Flink 集群中可能有多个作业同时执行,每个作业都有自己的 JobManager 组件。这三个组件都包含在 AppMaster 进程中。

  1. 执行脚本,会启动一个 Client 进程负责作业的编辑与提交。将用户编写的代码编译为一个 JobGraph,同时判断哪些 Operator 可以 Chain 到同一个 Task 中,进行优化。
  2. Client 将产生的 JobGraph 提交到集群中执行。此时有两种情况:
    a. 一种是类似于 Standalone 这种 Session 模式,AM 会预先启动。
    b. 另一种是 Per-Job 模式,AM 不会预先启动,此时 Client 将首先向资源管理系统 (如Yarn、K8S)申请资源来启动 AM。
  3. Client 向 Dispatcher 建立连接并提交作业。
  4. 作业到 Dispatcher 后,Dispatcher 会首先启动一个 JobManager 组件。
  5. JobManager 会向 ResourceManager 申请资源来启动作业中具体的任务。此时又分为两种模式。
  6. Per-Job 模式,ResourceManager 先向外部资源管理系统申请资源。
  7. 外部资源管理系统会启动 TaskExecutor。
  8. ResourceMangaer 获取 TaskExecutor 的 Solt 资源。
    a. Session 模式,TaskExecutor 已经预先启动,ResourceMangaer直接向 TaskExecutor 请求 Solt 资源。
    b. Per-Job 模式,TaskExecutor 刚刚启动,ResourceMangaer 向 TaskExecutor 请求 Solt 资源。
  9. ResourceManager 选择到空闲的 Slot 之后,就会通知相应的 TM “将该 Slot 分配分 JobManager XX”,然后 TaskExecutor 进行相应的记录后,会向 JobManager 进行注册。
  10. JobManager 收到 TaskExecutor 注册上来的 Slot ,将实际要执行的 Task 提交给 TaskManger。
  11. TaskExecutor 收到 JobManager 提交的 Task 之后,会启动一个新的线程来执行该 Task。Task 启动后就会开始进行预先指定的计算,并通过数据 Shuffle 模块互相交换数据。

这就是 Flink Runtime 层执行作业的基本流程。可以看出,Flink 支持两种不同的模式,即 Per-job 模式与 Session 模式。

1.3 Job 运行模式

Flink Runtime 核心机制_第3张图片

  • Per-job 模式下整个 Flink 集群只执行单个作业,即每个作业会独享 Dispatcher 和 ResourceManager 组件。此外,Per-job 模式下 AppMaster 和 TaskExecutor 都是按需申请的。因此,Per-job 模式更适合运行执行时间较长的大作业,也就是 ResourceManager 需要向 Yarn 来申请的开启 TaskManger,这些作业对稳定性要求较高,并且对申请资源的时间不敏感。
  • Session 模式下,Flink 预先启动 AppMaster 以及一组 TaskExecutor,然后在整个集群的生命周期中会执行多个作业。可以看出,Session 模式更适合规模小,执行时间短的作业。

2. 资源管理与任务调度

作业调度可以看做是对资源和任务进行匹配的过程。
在 Flink 中,资源是通过 Slot 来表示的,每个 Slot 可以用来执行不同的 Task。而在另一端,任务即 Job 中实际的 Task,它包含了待执行的用户逻辑。调度的主要目的就是为了给 Task 找到匹配的 Slot。
逻辑上来说,每个 Slot 都应该有一个向量来描述它所能提供的各种资源的量,每个 Task 也需要相应的说明它所需要的各种资源的量。但是实际上在 1.9 之前,Flink 是不支持细粒度的资源描述的,而是统一的认为每个 Slot 提供的资源和 Task 需要的资源都是相同的。从 1.9 开始,Flink 开始增加对细粒度的资源匹配的支持的实现,但这部分功能目前仍在完善中。

2.1 Slot 的管理和分配

Flink 中的资源是由 TaskExecutor 上的 Slot 来表示的。
下图是 Flink 资源管理功能各模块交互关系,其中 RM 是资源的管理者,JM是资源的申请者,TM是资源的实际提供者。
Flink Runtime 核心机制_第4张图片

2.1.1 Task 启动时,Slot 的调度分配流程

  • 在 ResourceManager 中,有一个子组件叫做 SlotManager,它维护了当前集群中所有 TaskExecutor 上的 Slot 的信息与状态,如该 Slot 在哪个 TaskExecutor 中,该 Slot 当前是否空闲等。
  • 当 JobManger 来为特定 Task 申请资源的时候,根据当前是 Per-job 还是 Session 模式,ResourceManager 可能会去申请资源来启动新的 TaskExecutor。
  • 当 TaskExecutor 启动之后,它会通过服务发现找到当前活跃的 ResourceManager 并进行注册。在注册信息中,会包含该 TaskExecutor中所有 Slot 的信息。
  • ResourceManager 收到注册信息后,其中的 SlotManager 就会记录下相应的 Slot 信息。当 JobManager 为某个 Task 来申请资源时, SlotManager 就会从当前空闲的 Slot 中按一定规则选择一个空闲的 Slot 进行分配。
  • 当分配完成后,RM 会首先向 TaskManager 发送 RPC 要求将选定的 Slot 分配给特定的 JobManager。
  • TaskManager 如果还没有执行过该 JobManager 的 Task 的话,它需要首先向相应的 JobManager 建立连接,然后发送提供 Slot 的 RPC 请求。
  • 在 JobManager 中,所有 Task 的请求会缓存到 SlotPool 中。当有 Slot 被提供之后,SlotPool 会从缓存的请求中选择相应的请求并结束相应的请求过程。

2.1.2 Task 执行结束后,Slot 的释放流程

  • 当 Task 结束之后,无论是正常结束还是异常结束,都会通知 JobManager 相应的结束状态,然后在 TaskManager 端将 Slot 标记为已占用但未执行任务的状态。
  • JobManager 会首先将相应的 Slot 缓存到 SlotPool 中,但不会立即释放。这种方式避免了如果将 Slot 直接还给 ResourceManager,在任务异常结束之后需要重启时,需要立刻重新申请 Slot 的问题。
  • 通过延时释放,Failover 的 Task 可以尽快调度回原来的 TaskManager,从而加快 Failover 的速度。
  • 当 SlotPool 中缓存的 Slot 超过指定的时间仍未使用时,SlotPool 就会发起释放该 Slot 的过程。
  • 与申请 Slot 的过程对应,SlotPool 会首先通知 TaskManager 来释放该 Slot,然后 TaskExecutor 通知 ResourceManager 该 Slot 已经被释放,从而最终完成释放的逻辑。

2.1.3 心跳

在分布式系统中,消息的丢失、错乱不可避免,这些问题会在分布式系统的组件中引入不一致状态,如果没有定时消息,那么组件无法从这些不一致状态中恢复。当组件之间长时间未收到对方的心跳时,就会认为对应的组件已经失效,并进入到 Failover 的流程。

  • 在 ResourceManager 和 TaskExecutor 之间还存在定时的心跳消息来同步 Slot 的状态。
  • ResourceManager 长时间未收到 TaskExecuto r 的心跳时,就会认为对应的组件已经失效。

2.2 Slot 描述描述

在社区版中,默认将每个 Slot 的大小看做相等的,同时一个 Task 使用一个 Slot,而在 Blink 开源的版本中,用具体的向量来描述 Slot 资源,使得 Slot 资源能够得到更好的分配。
Flink Runtime 核心机制_第5张图片

2.3 Slot Sharing

在 Slot 管理基础上,Flink 可以将 Task 调度到相应的 Slot 当中。Flink 尚未完全引入细粒度的资源匹配。
默认情况下,每个 Slot 可以分配给一个 Task。但是,这种方式在某些情况下会导致资源利用率不高。
假如 A、B、C 依次执行计算逻辑,那么给 A、B、C 分配分配单独的 Slot 就会导致资源利用率不高。为了解决这一问题,Flink 提供了 Share Slot 的机制。
Flink Runtime 核心机制_第6张图片

  • 基于 Share Slot,每个 Slot 中可以部署来自不同 JobVertex 的多个任务,但是不能部署来自同一个 JobVertex 的 Task。
  • 每个 Slot 中最多可以部署同一个 A、B 或 C 的 Task,但是可以同时部署 A、B 和 C 的各一个 Task。
  • 当单个 Task 占用资源较少时,Share Slot 可以提高资源利用率。 此外,Share Slot 也提供了一种简单的保持负载均衡的方式。

2.4 Job 的调度与 DAG 执行图

Job 的调度
Flink Runtime 核心机制_第7张图片

  1. Flink 程序的 API 映射成一个 StreamGraph,Client 端会将其优化为一个 JobGragph。
  2. Client 端会向 JobManager 提交一个 JobGraph,它代表了作业的逻辑结构。
  3. JobManager 负责维护作业中 Task 执行的状态。
  4. JobManager 会根据 JobGraph 按并发展开,从而得到 JobManager 中关键的 ExecutionGraph。ExecutionGraph 是 JobManager 中最核心的数据结构,其中维护着各个 Job 的具体状态。
  5. 与 JobGraph 相比,ExecutionGraph 中对于每个 Task 与中间结果均创建了对应的对象,从而可以维护这些实体的信息与状态。
  6. 最后 Job Manager 根据 ExucutionGraph 进行调度,在 TaskManager 上部署 Task 后形成的“图”,指导 TaskManager 运行。

DAG 执行图
Flink Runtime 核心机制_第8张图片

  • StreamGraph 逻辑流图
    是 Flink 程序中的 Stream API 直接映射成的数据流向图,用来表示程序的拓扑结构。
  • JobGraph
    StreamGraph 经过优化(将多个符合条件的节点 chain 在一起作为一个节点,减少数据在节点之间流动所需要的序列化、反序列化、传输消耗)
    JobGrapg 算是客户单提交的作业的 DAG 结构,是一个逻辑结构,不考虑并发。
  • ExecutionGraph
    JobMaster 实际维护的数据结构,是一个物理结构,考虑并发,是 JobGraph 的并行化版本,是调度层最核心的数据结构。
  • 物理执行图
    根据 ExecutionGraph 对 Job 进行调度后,在各个 TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构。

2.5 调度策略

目前 Flink 提供了两种基本的调度逻辑,即 Eager 调度与 Lazy From Source。
Eager 调度如其名子所示,它会在作业启动时申请资源将所有的 Task 调度起来。这种调度算法主要用来调度可能没有终止的流作业。
与之对应,Lazy From Source 则是从 Source 开始,按拓扑顺序来进行调度。简单来说,Lazy From Source 会先调度没有上游任务的 Source 任务,当这些任务执行完成时,它会将输出数据缓存到内存或者写入到磁盘中。这种调度模式更省资源。对于后续的任务,当它的前驱任务全部执行完成后,Flink 就会将这些任务调度起来。这些任务会从读取上游缓存的输出数据进行自己的计算。这一过程继续进行直到所有的任务完成计算。
Flink Runtime 核心机制_第9张图片

3. 错误恢复

Flink 作业的执行过程中,除正常执行的流程外,还有可能由于环境等原因导致各种类型的错误。整体上来说,错误可能分为两大类:Task 执行出现错误或 Flink 集群的 Master 出现错误。由于错误不可避免,为了提高可用性,Flink 需要提供自动错误恢复机制来进行重试。

3.1 Task Failover

Task 执行错误,Flink 提供了多种不同的错误恢复策略。

3.1.1 Restart-all

直接重启所有的 Task。对于 Flink 的流任务,由于 Flink 提供了 Checkpoint 机制,因此当任务重启后可以直接从上次的 Checkpoint 开始继续执行。因此这种方式更适合于流作业。
Flink Runtime 核心机制_第10张图片

3.1.2 Restart-individual

直接重启出错的任务,这种策略应用极为有限,它只适用于 Task 之间没有数据传输的情况。
Flink Runtime 核心机制_第11张图片

3.1.3 Restart-Region

适用于Pipeline 和 Blocking 都存在的作业,由于 Flink 的批作业没有 Checkpoint 机制,因此对于需要数据传输的作业,直接重启所有 Task 会导致作业从头计算,从而导致一定的性能问题。
Flink 在1.9中引入了一种新的Region-Based的Failover策略。
在一个 Flink 的 Batch 作业中 Task 之间存在两种数据传输方式,一种是 Pipeline 类型的方式,如下图中A->B之间的作业,这种方式上下游 Task 之间直接通过网络传输数据,因此需要上下游同时运行;另外一种是 Blocking 类型的试,如下图中B->C之间的作业,这种方式下,上游的 Task 会首先将数据进行缓存,因此上下游的 Task 可以单独执行。
基于这两种类型的传输,Flink 将 ExecutionGraph 中使用 Pipeline 方式传输数据的 Task 的子图叫做 Region,从而将整个 ExecutionGraph 划分为多个子图。可以看出,Region 内的 Task 必须同时重启,而不同 Region 的 Task 由于在 Region 边界存在 Blocking 的边,因此,可以单独重启下游 Region 中的 Task。
Flink Runtime 核心机制_第12张图片
针对上图中的情况,如果D1发现自身作业失败,则直接重启C1和D1;如果C1发现自身作业失败,也会直接重启C1和D1;但是如果C1读取上游的缓存数据失败,则需要重启A1、A2、B1、C1、D1。最后这种情况相当重启了两个 Region。
Flink Runtime 核心机制_第13张图片

3.2 Master Failover

  • 方式一:同时启动多个 Master,通过 ZK 进行选主。
  • 目前社区版使用的情况:杀掉所有 Task 的作业,重新启动新的作业。

4. 未来展望

Flink目前仍然在Runtime部分进行不断的迭代和更新。目前来看,Flink未来可能会在以下几个方式继续进行优化和扩展:

  • 更完善的资源管理

从 1.9 开始 Flink 开始了对细粒度资源匹配的支持。基于细粒度的资源匹配,用户可以为 TaskExecutor 和 Task 设置实际提供和使用的 CPU、内存等资源的数量,Flink 可以按照资源的使用情况进行调度。这一机制允许用户更大范围的控制作业的调度,从而为进一步提高资源利用率提供了基础。

  • 统一的 Stream 与 Batch

Flink 目前为流和批分别提供了 DataStream 和 DataSet 两套接口,在一些场景下会导致重复实现逻辑的问题。未来 Flink 会将流和批的接口都统一到 DataStream 之上。

  • 更灵活的调度策略

Flink 从 1.9 开始引入调度插件的支持,不仅仅使用 Eager 和 Lazy from souce,从而允许用户来扩展实现自己的调度逻辑。未来 Flink 也会提供更高性能的调度策略的实现。

  • Master Failover 的优化

目前 Flink 在 Master Failover 时需要重启整个作业,而实际上重启作业并不是必须的逻辑。Flink 未来会对 Master failover 进行进一步的优化来避免不必要的作业重启。

以上内容是对 https://www.bilibili.com/video/av52394455/ 的学习总结。

你可能感兴趣的:(Flink Runtime 核心机制)