前言
本文主要讲述Flink的整体架构,以及流处理任务涉及的各个算子的调度编排机制。为模仿实现一个简易流处理引擎作下铺垫。具体流处理引擎编写的部分也将在后续以专栏形式进行整理发布。
Flink 是一个分布式系统,需要有效分配和管理计算资源才能执行流式应用程序,它集成了所有常见的集群资源管理器,如 Hadoop YARN、Apache Mesos 和 Kubernetes,但也可以设置为作为独立集群甚至作为库运行。
下文包含 Flink 架构的概述,并描述其主要组件如何交互以执行应用程序并从故障中恢复。
一、Flink集群剖析
Flink 运行时由两种类型的进程组成:一个JobManager和一个或多个TaskManagers。
Client不是运行时和程序执行的一部分,而是用于准备数据流并将其发送到 JobManager。之后,客户端可以断开连接,或保持连接以接收进度报告。客户端可以作为触发执行的 Java/Scala 程序的一部分运行,也可以在命令行进程中运行./bin/flink run ...
JobManager 和 TaskManagers 可以通过多种方式启动:直接在机器上作为独立集群、在容器中或由YARN或Mesos等资源框架管理。TaskManagers 连接到 JobManagers,宣布自己可用,并被分配工作。
二、JobManager
JobManager有许多与协调 Flink 应用程序的分布式执行相关的职责:它决定何时调度下一个任务(或一组任务),对已完成的任务或执行失败做出反应,协调检查点,并协调故障恢复等。这个过程包括三个不同的部分:
ResourceManager
ResourceManager负责 Flink 集群中的资源释放/分配和供应——它管理任务槽,这是 Flink 集群中资源调度的单元(参见TaskManagers)。Flink 为不同的环境和资源提供者(如 YARN、Mesos、Kubernetes 和独立部署)实现了多个 ResourceManager。在独立设置中,ResourceManager 只能分配可用 TaskManager 的插槽,不能自行启动新的 TaskManager。
Dispatcher
Dispatcher提供了一个 REST 接口来提交 Flink 应用程序执行,并为每个提交的作业启动一个新的 JobMaster。它还运行 Flink WebUI 以提供有关作业执行的信息。
JobMaster
JobMaster负责管理单个 JobGraph的执行。多个作业可以在 Flink 集群中同时运行,每个作业都有自己的 JobMaster。
总是至少有一个 JobManager。一个高可用性设置可能有多个JobManager,其中一个始终是Leader,其他是备用的。
三、TaskManager
TaskManagers(也称为workers )执行数据流的任务,缓冲和交换数据流。
必须始终至少有一个 TaskManager。TaskManager 中资源调度的最小单位是一个任务槽。TaskManager 中的任务槽数表示并发处理任务的数量。请注意,多个operator可以在一个任务槽中执行。
四、Tasks and Operator Chain
对于分布式执行,Flink将 operator 子任务链在一起形成任务。每个任务由一个线程执行。将operator链接到任务中是一种有用的优化:它减少了线程到线程切换和缓冲的开销,并在降低延迟的同时提高了整体吞吐量。
下图中的示例数据流使用五个子任务执行,因此使用五个并行线程。
五、Task Slots and Resources
每个worker(TaskManager)都是一个JVM进程,可以在不同的线程中执行一个或多个子任务。为了控制 TaskManager 接受多少任务,它有所谓的任务槽(至少一个)。
每个任务槽代表 TaskManager 的固定资源子集。例如,具有三个插槽的 TaskManager 会将其托管内存的 1/3 专用于每个插槽。分配资源意味着子任务不会与其他作业的子任务竞争托管内存,而是保留一定数量的托管内存。请注意,这里没有发生 CPU 隔离;目前插槽仅分离任务的托管内存。
通过调整任务槽的数量,用户可以定义子任务如何相互隔离。每个 TaskManager 有一个插槽意味着每个任务组在单独的 JVM 中运行(例如,可以在单独的容器中启动)。拥有多个插槽意味着更多的子任务共享同一个 JVM。同一 JVM 中的任务共享 TCP 连接(通过多路复用)和心跳消息。它们还可以共享数据集和数据结构,从而减少每个任务的开销。
默认情况下,Flink 允许子任务共享槽,即使它们是不同任务的子任务,只要它们来自同一个作业。结果是一个槽可以容纳整个工作流水线。允许此插槽共享有两个主要好处:
- Flink 集群需要与作业中使用的最高并行度一样多的任务槽。无需计算程序总共包含多少个任务(具有不同的并行度)。
- 更容易获得更好的资源利用率。如果没有槽共享,非密集的source/map()子任务将阻塞与资源密集的窗口子任务一样多的资源。通过槽共享,将我们示例中的基本并行度从 2 增加到 6 可以充分利用槽资源,同时确保繁重的子任务在 TaskManager 之间公平分布。
六、Flink Application Execution
Flink 应用程序是从其main()
方法生成一个或多个 Flink 作业的任何用户程序。这些作业的执行可以发生在本地 JVM ( LocalEnvironment
) 或具有多台机器的远程集群设置 ( RemoteEnvironment
) 中。对于每个程序,都ExecutionEnvironment提供了控制作业执行(例如设置并行度)和与外界交互的方法。
Flink Application 的作业可以提交到长时间运行的 Flink Session Cluster、专用的Flink Job Cluster或 Flink Application Cluster。这些选项之间的区别主要与集群的生命周期和资源隔离保证有关。
Flink Session Cluster
- 集群生命周期:在 Flink 会话集群中,客户端连接到一个预先存在的、长时间运行的集群,该集群可以接受多个作业提交。即使在所有作业完成后,集群(和 JobManager)仍将继续运行,直到手动停止会话。因此,Flink Session Cluster 的生命周期不受任何 Flink Job 的生命周期的约束。
- 资源隔离:TaskManager 插槽由 ResourceManager 在作业提交时分配,并在作业完成后释放。因为所有作业都共享同一个集群,所以集群资源存在一些竞争——比如提交作业阶段的网络带宽。这种共享设置的一个限制是,如果一个 TaskManager 崩溃,那么所有在该 TaskManager 上运行的任务都会失败;同理,如果 JobManager 发生致命错误,会影响集群中运行的所有作业。
- 其他注意事项:拥有一个预先存在的集群可以节省大量申请资源和启动 TaskManager 的时间。这在作业的执行时间非常短且启动时间过长会对端到端用户体验产生负面影响的情况下很重要——就像短查询的交互式分析一样,在这种情况下,希望作业可以快速使用现有资源执行计算。
Flink Job Cluster
- 集群生命周期:在 Flink 作业集群中,可用的集群管理器(如 YARN 或 Kubernetes)用于为每个提交的作业启动一个集群,并且该集群仅可用于该作业。在这里,客户端首先向集群管理器请求资源以启动 JobManager,并将作业提交给运行在该进程内的 Dispatcher。然后根据作业的资源需求延迟分配 TaskManager。一旦作业完成,Flink Job Cluster 就会被拆除。
- 资源隔离:JobManager 中的致命错误仅影响在该 Flink 作业集群中运行的一项作业。
- 其他注意事项:由于 ResourceManager 必须申请并等待外部资源管理组件启动 TaskManager 进程并分配资源,所以 Flink Job Clusters 更适合长时间运行、对稳定性要求高且不敏感的大型作业。
Flink Application Cluster
- 集群生命周期:一个 Flink 应用程序集群是一个专用的 Flink 集群,它只执行来自一个 Flink 应用程序的作业,并且该
main()
方法在集群上而不是客户端上运行。作业提交是一步完成的:不需要先启动一个 Flink 集群,再将作业提交到现有的集群会话;相反,您将应用程序逻辑和依赖项打包到可执行作业 JAR 中,集群入口点 (ApplicationClusterEntryPoint
) 负责调用该main()
方法以提取 JobGraph。例如,这允许您像在 Kubernetes 上部署任何其他应用程序一样部署 Flink 应用程序。因此,Flink Application Cluster 的生命周期与 Flink Application 的生命周期绑定。 - 资源隔离:在 Flink Application Cluster 中,ResourceManager 和 Dispatcher 被限定为单个 Flink Application,它提供了比 Flink Session Cluster 更好的关注点分离。