1. 从你的代码到分布式任务:Flink调度全景图
如果你用过Flink,肯定写过类似 dataStream.map(...).filter(...).keyBy(...).window(...) 这样的代码。但你想过吗,当你点击“运行”按钮后,这一行行优雅的API背后,究竟发生了什么?你的代码是如何被拆解、重组,最终变成成千上万个在几十台机器上并行奔跑的小任务(Task)的?
我自己在早期使用Flink时,总觉得它像个“黑盒”——代码写好了,任务跑起来了,但中间的过程云里雾里。一旦作业失败或者性能出现瓶颈,排查起来就非常吃力,只能对着日志和监控图瞎猜。后来我下定决心,一定要把Flink从“提交”到“执行”这条链路的源码啃下来。这个过程虽然痛苦,但彻底搞明白之后,再遇到任何调度问题,心里都跟明镜似的。今天,我就把自己从Flink 1.19.0源码里梳理出来的这条“任务诞生之旅”分享给你,我会尽量用大白话和实际案例,带你走通从ExecutionGraph构建到Task调度的完整链路。
简单来说,Flink的核心调度旅程可以概括为“三级转换,两级调度”。你的DataStream API代码,首先会在客户端被转换成一张描述计算逻辑的StreamGraph;接着,为了优化性能,Flink会将可以合并的算子“链”在一起,形成JobGraph;当JobGraph被提交到集群后,JobMaster会将其展开成真正可并行执行的ExecutionGraph。到这里,还只是“纸上谈兵”。接下来才是重头戏:调度器(Scheduler)会以SchedulingPipelinedRegion为单位,向资源管理器(ResourceManager)申请具体的Slot资源,最终将一个个Task部署到TaskManager上执行。我们今天要深度剖析的,正是从ExecutionGraph生成之后,到Task在Slot里跑起来的这段核心调度链路。理解了它,你就能真正掌握Flink作业的命脉。
2. ExecutionGraph:并行世界的蓝图
JobGraph可以看作是任务的“逻辑计划”,它定义了算子和它们之间的连接关系,但还没有决定每个算子要启动多少个实例、每个实例具体在哪里执行。而ExecutionGraph,就是这份逻辑计划的“并行化展开版本”,是调度器行动的终极蓝图。
2.1 构建并行化的骨架
想象一下,你有一个Map算子,你设置它的并行度(parallelism)为5。在JobGraph里,它只是一个叫“Map”的JobVertex节点。但在ExecutionGraph里,这个节点会“分裂”成5个ExecutionVertex,每个ExecutionVertex代表一个并行的子任务实例。同时,这个算子产生的数据输出,在JobGraph里是一个IntermediateDataSet,在ExecutionGraph里则会对应一个IntermediateResult,而这个IntermediateResult又会包含5个IntermediateResultPartition,每个Partition对应一个并行子任务的输出。
我翻看DefaultExecutionGraph.attachJobVertices()方法时,清晰地看到了这个展开过程。源码会遍历JobGraph中的所有JobVertex,为每一个创建对应的ExecutionJobVertex。这个ExecutionJobVertex是个“包工头”,它负责管理自己手下所有并行子任务(ExecutionVertex)。在ExecutionJobVertex.initialize()方法里,“包工头”会做两件大事:一是为自己的每个输出数据集创建IntermediateResu


被折叠的 条评论
为什么被折叠?



