☰
Flink 任务调度 Graph 源码深度剖析:从 StreamGraph 到 ExecutionGraph 的三层演进
2026/10/15 3:39:21 网站建设 项目流程

前面讲了 SlotManager 分配 Slot 和 SlotPool 申请资源的源码实现,这篇聚焦 Flink 任务调度 Graph 的源码实现。Flink 任务从用户代码到实际执行,经历了三层 Graph 的演进:StreamGraph(流图)→ JobGraph(作业图)→ ExecutionGraph(执行图)。你有没有想过,用户写的 DataStream API 是如何变成可执行的任务的?算子链优化是如何合并算子的?并行度是如何展开的?调度器是如何调度 Execution 的?这篇从源码级别跟踪 Flink 任务调度 Graph 的完整演进链路。


一、任务调度 Graph 整体架构

下面这张图是任务调度 Graph 整体架构,包括三层 Graph 层级关系、调度关系和核心类。

Flink 任务调度 Graph 分为三层,从逻辑到物理逐步演进:

  • StreamGraph(流图):用户 API 构建的逻辑执行图,描述算子拓扑。包含 StreamNode(算子节点)和 StreamEdge(数据流边),从 Transformation 列表构建。
  • JobGraph(作业图):提交到 JobManager 的执行图,算子链合并后的物理图。包含 JobVertex(作业顶点,对应算子链)、JobEdge(作业边)、IntermediateDataSet(中间数据集)。
  • ExecutionGraph(执行图):JobMaster 内部调度图,按并行度展开的可执行图。包含 ExecutionJobVertex(执行作业顶点)、ExecutionVertex(执行顶点,对应并行子任务)、Execution(执行实例,具体执行)。

三层 Graph 的演进关系:StreamGraph 经过算子链优化(Operator Chain)生成 JobGraph,JobGraph 经过并行度展开(Parallelism Expansion)生成 ExecutionGraph。ExecutionGraph 构建完成后,SchedulerNG 调度 Execution,通过 SlotPool 申请 Slot,SlotManager 分配 Slot,最终部署 Task 到 TaskManager 执行。


二、JobGraph 构建流程

下面这张图是 JobGraph 构建流程,包括5步构建流程、算子链优化对比、组件关系和核心类。

JobGraph 构建分为5步:

第1步:构建 StreamGraph。用户调用 env.execute() 时,StreamExecutionEnvironment 通过 StreamGraphGenerator 从 Transformation 列表构建 StreamGraph。StreamGraph 包含 StreamNode(算子节点)和 StreamEdge(数据流边),描述用户定义的逻辑执行拓扑。每个 Transformation 对应一个或多个 StreamNode,Transformation 之间的依赖关系对应 StreamEdge。

第2步:算子链优化(Operator Chain)。JobGraphGenerator 遍历 StreamGraph,判断哪些 StreamNode 可以链接成算子链。链接条件:上下游并行度相同、数据分发模式为 forward、没有禁用算子链(disableChaining)、没有 slot sharing group 隔离。可链接的算子合并为一个 JobVertex,减少线程切换和数据序列化开销。

第3步:创建 JobVertex。每个算子链(或独立算子)创建一个 JobVertex,包含算子配置、并行度、输入输出配置、SlotSharingGroup 等信息。JobVertex 是 JobGraph 的基本调度单元,对应一个物理执行任务。JobVertex 包含 JobVertexInput(输入配置)和 JobVertexOutput(输出配置)。

第4步:创建 JobEdge 和 IntermediateDataSet。JobVertex 之间通过 JobEdge 连接,JobEdge 指向源 JobVertex 的 IntermediateDataSet(中间数据集)。IntermediateDataSet 表示一个 JobVertex 的输出结果,包含数据分区策略(forward/rebalance/keyBy/broadcast 等)。JobEdge 定义了数据从上游 JobVertex 到下游 JobVertex 的传输方式。

第5步:生成 JobGraph 并提交。所有 JobVertex、JobEdge、IntermediateDataSet 创建完成后,组装成完整的 JobGraph。JobGraph 包含作业 ID、作业名称、配置信息、所有 JobVertex 和依赖关系。通过 Client 提交到 JobManager(Dispatcher),Dispatcher 启动 JobMaster,JobMaster 将 JobGraph 构建为 ExecutionGraph 进行调度执行。

算子链优化的价值

算子链优化是 Flink 重要的性能优化手段。优化前,Source、Map、Filter、Sink 四个算子各自独立,需要4个线程,3次序列化和网络传输,性能开销大。优化后,四个算子合并为一个算子链,只需要1个线程,0次序列化,数据在内存中直接传递,性能显著提升。

算子链的链接条件需要同时满足:

  • 上下游并行度相同
  • 数据分发模式为 forward(一对一)
  • 没有调用 disableChaining() 禁用算子链
  • 上下游属于同一个 SlotSharingGroup

三、ExecutionGraph 构建与调度

下面这张图是 ExecutionGraph 构建与调度,包括6步构建流程、并行度展开、Execution 状态机和配置最佳实践。

ExecutionGraph 构建与调度分为6步:

第1步:接收 JobGraph。Dispatcher 启动 JobMaster,JobMaster 接收 Client 提交的 JobGraph。JobGraph 包含所有 JobVertex、JobEdge、IntermediateDataSet 和作业配置。JobMaster 创建 ExecutionGraphBuilder,准备构建 ExecutionGraph。

第2步:创建 ExecutionJobVertex。遍历 JobGraph 中的每个 JobVertex,创建对应的 ExecutionJobVertex。ExecutionJobVertex 是 ExecutionGraph 的基本调度单元,包含并行度、算子配置、输入输出配置、SlotSharingGroup 等信息。一个 ExecutionJobVertex 对应一个 JobVertex。

第3步:并行度展开创建 ExecutionVertex。每个 ExecutionJobVertex 按并行度展开,创建 parallelism 个 ExecutionVertex。ExecutionVertex 表示一个并行子任务,包含子任务索引、输入输出配置、分配的 Slot 信息。ExecutionVertex 是实际调度和执行的基本单元,对应一个 TaskManager 上的一个 Task。

第4步:连接 ExecutionEdge。根据 JobEdge 和 IntermediateDataSet,创建 ExecutionEdge 连接上下游 ExecutionVertex。ExecutionEdge 定义了数据从上游 ExecutionVertex 到下游 ExecutionVertex 的传输方式(forward/rebalance/keyBy/broadcast 等)。ExecutionGraph 的拓扑结构完整构建,所有依赖关系建立。

第5步:SchedulerNG 调度 Execution。ExecutionGraph 构建完成后,SchedulerNG(DefaultScheduler)开始调度。每个 ExecutionVertex 创建一个 Execution 实例,表示一次具体的任务执行。SchedulerNG 按拓扑顺序调度 Execution,先调度 Source,再调度下游算子。调度时调用 SlotPool.requestSlot() 申请 Slot。

第6步:部署 Task 到 TaskManager。Slot 分配成功后,Execution 通过 TaskExecutorGateway 发送 submitTask 请求,将 Task 部署到对应的 TaskManager。TaskManager 创建 Task 实例,分配到 Slot 上启动执行。Task 执行过程中,ExecutionGraph 跟踪每个 Execution 的状态。

并行度展开机制

并行度展开是 ExecutionGraph 的核心机制。一个 ExecutionJobVertex(对应 JobVertex,并行度=4)按并行度展开为4个 ExecutionVertex(子任务索引0-3),每个 ExecutionVertex 每次执行创建一个 Execution 实例。ExecutionVertex 是静态的(对应一个并行子任务),Execution 是动态的(对应一次具体的执行,可能因故障重试而多次创建)。

Execution 状态机

Execution 有7种核心状态,状态转换严格按顺序执行:

  • CREATED:已创建,Execution 实例创建完成
  • SCHEDULED:已调度,SchedulerNG 已调度,等待 Slot 分配
  • DEPLOYING:部署中,Slot 已分配,正在向 TaskManager 部署 Task
  • RUNNING:运行中,Task 已启动,正在执行
  • FINISHED:已完成,Task 执行成功完成
  • FAILED:失败,Task 执行失败
  • CANCELED:已取消,Task 被取消

四、核心类源码剖析

4.1 StreamGraph 流图

publicclassStreamGraph{privatefinalStringjobName;privatefinalMap<Integer,StreamNode>streamNodes;privatefinalSet<StreamEdge>streamEdges;privatefinalMap<Integer,List<StreamEdge>>inEdges;privatefinalMap<Integer,List<StreamEdge>>outEdges;publicvoidaddOperator(IntegervertexID,StringslotSharingGroup,StringcoLocationGroup,StreamOperatorFactoryoperatorFactory,TypeInformationinTypeInfo,TypeInformationoutTypeInfo,StringoperatorName){// 创建 StreamNode 并加入映射StreamNodevertex=newStreamNode(vertexID,slotSharingGroup,coLocationGroup,operatorFactory,operatorName,inTypeInfo,outTypeInfo);streamNodes.put(vertexID,vertex);}publicvoidaddEdge(IntegersourceId,IntegertargetId,inttypeNumber,StringoutputName){// 创建 StreamEdge 连接两个 StreamNodeStreamNodesource=streamNodes.get(sourceId);StreamNodetarget=streamNodes.get(targetId);StreamEdgeedge=newStreamEdge(source,target,typeNumber,outputName);streamEdges.add(edge);outEdges.get(sourceId).add(edge);inEdges.get(targetId).add(edge);}publicStreamNodegetStreamNode(IntegervertexID){returnstreamNodes.get(vertexID);}}publicclassStreamNode{privatefinalintid;privatefinalStreamOperatorFactoryoperatorFactory;privatefinalStringoperatorName;privateintparallelism;privateStringslotSharingGroup;privateList<StreamEdge>inEdges;privateList<StreamEdge>outEdges;}

StreamGraph 是逻辑执行图,包含 StreamNode(算子节点)和 StreamEdge(数据流边)。addOperator 方法创建 StreamNode 并加入映射,addEdge 方法创建 StreamEdge 连接两个 StreamNode。StreamNode 包含算子工厂、并行度、SlotSharingGroup、输入输出边等信息。

4.2 JobGraph 作业图

publicclassJobGraph{privatefinalJobIDjobID;privateStringname;privatefinalMap<JobVertexID,JobVertex>taskVertices;privatefinalList<JobEdge>jobEdges;publicvoidaddVertex(JobVertexvertex){// 添加 JobVertex 到作业图taskVertices.put(vertex.getID(),vertex);}publicvoidaddEdge(JobEdgeedge){// 添加 JobEdge 到作业图jobEdges.add(edge);}publicJobVertexfindVertexByID(JobVertexIDid){returntaskVertices.get(id);}}publicclassJobVertex{privatefinalJobVertexIDid;privateStringname;privateintparallelism;privateStreamConfigconfiguration;privateStringslotSharingGroup;privateList<JobEdge>inputs;privateList<IntermediateDataSet>results;publicvoidconnectNewDataSetAsInput(JobVertexinput,DistributionPatterndistributionPattern,booleanisBroadcast){// 创建 IntermediateDataSet 和 JobEdgeIntermediateDataSetdataSet=input.createAndAddResultDataSet(this,distributionPattern);JobEdgeedge=newJobEdge(dataSet,this,distributionPattern);inputs.add(edge);}publicIntermediateDataSetcreateAndAddResultDataSet(JobVertexconsumer,DistributionPatternpattern){// 创建中间数据集IntermediateDataSetdataSet=newIntermediateDataSet(newIntermediateDataSetID(),pattern,this);results.add(dataSet);returndataSet;}}publicclassIntermediateDataSet{privatefinalIntermediateDataSetIDid;privatefinalDistributionPatterndistributionPattern;privatefinalJobVertexproducer;privatefinalList<JobEdge>consumers;}publicclassJobEdge{privatefinalIntermediateDataSetsource;privatefinalJobVertextarget;privatefinalDistributionPatterndistributionPattern;}

JobGraph 是提交到 JobManager 的物理执行图,包含 JobVertex(作业顶点)、JobEdge(作业边)、IntermediateDataSet(中间数据集)。JobVertex.connectNewDataSetAsInput 方法创建 IntermediateDataSet 和 JobEdge,连接上下游 JobVertex。IntermediateDataSet 表示一个 JobVertex 的输出结果,包含数据分区策略。JobEdge 定义了数据传输方式。

4.3 ExecutionGraph 执行图

publicclassExecutionGraph{privatefinalJobIDjobID;privateStringjobName;privatefinalMap<JobVertexID,ExecutionJobVertex>tasks;privatefinalList<ExecutionVertex>allVertices;privateSchedulerNGscheduler;publicvoidattachJobGraph(List<JobVertex>topologiallySorted)throwsException{// 遍历 JobVertex,创建 ExecutionJobVertexfor(JobVertexjobVertex:topologiallySorted){ExecutionJobVertexejv=newExecutionJobVertex(this,jobVertex,jobVertex.getParallelism());tasks.put(jobVertex.getID(),ejv);}// 连接 ExecutionEdgefor(ExecutionJobVertexejv:tasks.values()){ejv.connectToPredecessors();}}}publicclassExecutionJobVertex{privatefinalExecutionGraphgraph;privatefinalJobVertexjobVertex;privatefinalintparallelism;privatefinalExecutionVertex[]taskVertices;publicExecutionJobVertex(ExecutionGraphgraph,JobVertexjobVertex,intparallelism){this.graph=graph;this.jobVertex=jobVertex;this.parallelism=parallelism;// 按并行度展开,创建 ExecutionVertex 数组this.taskVertices=newExecutionVertex[parallelism];for(inti=0;i<parallelism;i++){taskVertices[i]=newExecutionVertex(this,i,jobVertex.getConfiguration());}}publicvoidconnectToPredecessors(){// 连接上下游 ExecutionVertexfor(inti=0;i<parallelism;i++){taskVertices[i].connectSource();}}publicExecutionVertex[]getTaskVertices(){returntaskVertices;}}publicclassExecutionVertex{privatefinalExecutionJobVertexjobVertex;privatefinalintsubTaskIndex;privatefinalStreamConfigconfiguration;privateExecutioncurrentExecution;privateSlotAndLocalityassignedSlot;publicExecutionVertex(ExecutionJobVertexjobVertex,intsubTaskIndex,StreamConfigconfiguration){this.jobVertex=jobVertex;this.subTaskIndex=subTaskIndex;this.configuration=configuration;}publicvoiddeploy()throwsException{// 创建 Execution 实例并部署currentExecution=newExecution(this,subTaskIndex);currentExecution.deploy();}publicExecutiongetCurrentExecution(){returncurrentExecution;}}publicclassExecution{privatefinalExecutionVertexvertex;privatefinalintattemptNumber;privateExecutionStatestate;privateCompletableFuture<Void>releaseFuture;publicExecution(ExecutionVertexvertex,intattemptNumber){this.vertex=vertex;this.attemptNumber=attemptNumber;this.state=ExecutionState.CREATED;}publicvoiddeploy()throwsException{// 状态转换:CREATED → SCHEDULED → DEPLOYING → RUNNINGcheckState(state==ExecutionState.CREATED);state=ExecutionState.SCHEDULED;// 申请 Slot,部署 Taskstate=ExecutionState.DEPLOYING;// TaskManager.submitTask()state=ExecutionState.RUNNING;}publicvoidmarkFinished(){checkState(state==ExecutionState.RUNNING);state=ExecutionState.FINISHED;}}publicenumExecutionState{CREATED,SCHEDULED,DEPLOYING,RUNNING,FINISHED,FAILED,CANCELED}

ExecutionGraph 是 JobMaster 内部的可执行调度图。attachJobGraph 方法遍历 JobVertex,创建 ExecutionJobVertex,然后连接 ExecutionEdge。ExecutionJobVertex 按并行度展开创建 ExecutionVertex 数组,每个 ExecutionVertex 对应一个并行子任务。ExecutionVertex.deploy 方法创建 Execution 实例,Execution 管理任务执行的状态机(CREATED→SCHEDULED→DEPLOYING→RUNNING→FINISHED)。


五、关键配置参数

# 默认并行度parallelism.default:1# 调度器类型jobmanager.scheduler:adaptive# 算子链优化pipeline.operator-chaining:true# 故障恢复策略jobmanager.execution.failover-strategy:region# 重启策略类型restart-strategy.type:fixed-delay# 固定延迟重启次数restart-strategy.fixed-delay.attempts:3# 固定延迟重启间隔restart-strategy.fixed-delay.delay:10s# 执行模式execution.attached:true# Slot 请求超时slotmanager.request-timeout:5min# 每个 TaskManager 的 Slot 数taskmanager.numberOfTaskSlots:1

关键配置说明:

  • parallelism.default:默认并行度,所有算子的默认并行度,可在代码中单独设置。
  • jobmanager.scheduler:调度器类型,adaptive 支持动态调整并行度,根据可用 Slot 数自动调整。
  • pipeline.operator-chaining:算子链优化,默认 true,启用后可链接的算子合并为一个 JobVertex。
  • jobmanager.execution.failover-strategy:故障恢复策略,region 只重启受影响的 Task 区域,减少故障恢复时间。
  • restart-strategy:重启策略,fixed-delay 固定延迟重试,failure-rate 失败率重试。
  • slotmanager.request-timeout:Slot 请求超时,默认5分钟,大作业可调大到10-15分钟。

六、常见问题与最佳实践

常见问题

  1. 算子链未生效

    • 原因:并行度不同、非 forward 分发、禁用算子链、不同 SlotSharingGroup。
    • 解决:检查并行度配置,确认分发模式为 forward,移除 disableChaining(),确认 SlotSharingGroup 配置。
  2. JobVertex 数量过多

    • 原因:算子链优化未生效,每个算子独立 JobVertex,导致调度开销大。
    • 解决:启用算子链,合并可链接算子,减少 JobVertex 数量。
  3. Execution 调度延迟

    • 原因:Slot 不足、调度器配置不当、依赖关系复杂。
    • 解决:增加 TM 数量,使用 adaptive 调度器,优化算子拓扑,检查 Slot 分配状态。
  4. 部署 Task 失败

    • 原因:TaskManager 无响应、网络异常、资源不足。
    • 解决:检查 TM 心跳,确认网络连通,增加资源,查看 TM 日志。
  5. Execution 状态异常

    • 原因:状态转换非法、并发问题、故障恢复失败。
    • 解决:检查日志,重启 JM,配置重启策略,查看 ExecutionGraph 状态。
  6. SlotSharingGroup 隔离

    • 原因:不同 SlotSharingGroup 的算子不能共享 Slot,导致资源浪费。
    • 解决:合理配置 SlotSharingGroup,避免不必要的隔离,提高资源利用率。

最佳实践

  1. 合理设置并行度:根据数据量和资源设置并行度,避免并行度过高导致调度开销大,或过低导致处理能力不足。Source 并行度受数据源分片数限制。

  2. 启用算子链:默认启用算子链,减少线程切换和序列化开销,提升性能。只有在需要隔离或调试时才禁用算子链。

  3. 使用 adaptive 调度器:adaptive 调度器支持动态调整并行度,根据可用 Slot 数自动调整,提高资源利用率和作业稳定性。适合资源波动较大的环境。

  4. 配置 region 故障恢复:region 策略只重启受影响的 Task 区域,而不是整个作业,减少故障恢复时间和资源消耗。适合大多数流处理作业。

  5. 监控 Execution 状态:关注 Execution 状态变化,及时发现调度延迟和部署失败问题。监控 SCHEDULED 状态持续时间,过长说明 Slot 分配延迟。

  6. 合理配置 SlotSharingGroup:默认所有算子共享同一个 SlotSharingGroup,提高资源利用率。只有在需要资源隔离时才配置独立的 SlotSharingGroup。

  7. 优化算子拓扑:减少不必要的 shuffle,使用 forward 分发模式,促进算子链合并。避免数据倾斜,合理设计 keyBy 字段。


七、总结

Flink 任务调度 Graph 经历三层演进:StreamGraph(流图,逻辑执行图)→ JobGraph(作业图,算子链合并后的物理图)→ ExecutionGraph(执行图,按并行度展开的可执行图)。StreamGraph 从 Transformation 构建,包含 StreamNode 和 StreamEdge。JobGraph 经过算子链优化,包含 JobVertex、JobEdge、IntermediateDataSet。ExecutionGraph 按并行度展开,包含 ExecutionJobVertex、ExecutionVertex、Execution,是实际调度和执行的图。

核心类包括 StreamGraph(逻辑执行图)、JobGraph(物理执行图)、JobVertex(作业顶点)、JobEdge(作业边)、IntermediateDataSet(中间数据集)、ExecutionGraph(可执行调度图)、ExecutionJobVertex(执行作业顶点)、ExecutionVertex(执行顶点,并行子任务)、Execution(执行实例,状态机跟踪)。算子链优化合并可链接的算子,减少线程切换和序列化开销。并行度展开将 ExecutionJobVertex 展开为多个 ExecutionVertex。Execution 状态机跟踪 CREATED→SCHEDULED→DEPLOYING→RUNNING→FINISHED。

理解任务调度 Graph 的三层演进和核心类实现,有助于排查算子链未生效、JobVertex 数量过多、调度延迟、部署失败、状态异常等常见问题,为 Flink 作业的稳定运行提供保障。合理设置并行度、启用算子链、使用 adaptive 调度器、配置 region 故障恢复、监控 Execution 状态,可以显著提高作业的性能和稳定性。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询