生成作业图
在分析完了流处理程序生成的流图(StreamGraph)以及批处理程序生成的优化后的计划(OptimizedPlan)之后,下一步就是生成它们面向Flink运行时执行引擎的共同抽象——作业图(JobGraph)。
什么是作业图
作业图(JobGraph)是唯一被Flink的数据流引擎所识别的表述作业的数据结构,也正是这一共同的抽象体现了流处理和批处理在运行时的统一。
相比流图(StreamGraph)以及批处理优化计划(OptimizedPlan),JobGraph发生了一些变化,已经不完全是“静态”的数据结构了,因为它加入了中间结果集(IntermediateDataSet)这一“动态”概念。
作业顶点(JobVertex)、中间数据集(IntermediateDataSet)、作业边(JobEdge)是组成JobGraph的基本元素。这三个对象彼此之间互为依赖:
- 一个JobVertex关联着若干个JobEdge作为输入端以及若干个IntermediateDataSet作为其生产的结果集;
- 一个IntermediateDataSet关联着一个JobVertex作为生产者以及若干个JobEdge作为消费者;
- 一个JobEdge关联着一个IntermediateDataSet可认为是源以及一个JobVertex可认为是目标消费者;
因此一个JobGraph可能的图形化表示如下:
那么JobGraph是怎么组织并存储这些元素的呢?其实JobGraph只以Map的形式存储了所有的JobVertex,键是JobVertexID:
private final Map<JobVertexID, JobVertex> taskVertices = new LinkedHashMap<JobVertexID, JobVertex>();
至于其它的元素,通过JobVertex都可以根据关系找寻到。
JobGraph包含了如下这些属性:
- 描述作业相关的信息,比如上面的顶点、作业编号、名称等;
- 用户程序包相关的信息,比如类路径等;
- 执行的一些配置信息,比如异步快照的配置、会话超时、是否允许排队调度等;
绝大部分的实例方法都是维护这些属性的。
需要注意的是,用于迭代的反馈边(feedback edge)当前并不体现在JobGraph中,而是被内嵌在特殊的JobVertex中通过反馈信道(feedback channel)在它们之间建立关系。
流图生成作业图
这篇文章我们来分析流处理程序是如何从之前的Stream生成JobGraph的。这部分的实现位于类StreamingJobGraphGenerator中,它是流处理程序的JobGraph生成器,其核心是createJobGraph方法,它体现了生成JobGraph的主干调用,实现代码如下:
public JobGraph createJobGraph() {
//创建一个JobGraph实例对象
jobGraph = new JobGraph(streamGraph.getJobName());
//设置对task的调度模式为ALL,即所有的算子立即同时启动
jobGraph.setScheduleMode(ScheduleMode.ALL);
//对用于辅助生成JobGraph的一些实例变量进行初始化
init();
//给StreamGraph的每个StreamNode生成一个hash值,该hash值在节点不发生改变的情况下多次生成始终是一致的,
//可用来判断节点在多次提交时是否产生了变化并且该值也将作为JobVertex的ID
Map<Integer, byte[]> hashes = traverseStreamGraphAndGenerateHashes();
//基于StreamGraph从所有的source开始构建task chain
setChaining(hashes);
//给顶点设置物理边(入边)
setPhysicalEdges();
//为每个JobVertex设置slotShareGroup,同时为迭代的source/sink对设置CoLocationGroup
setSlotSharing();
//配置检查点
configureCheckpointing();
//配置重启策略
configureRestartStrategy();
//传递执行配置
jobGraph.setExecutionConfig(streamGraph.getExecutionConfig());
return jobGraph;
}
接下来我们挨个对几个关键的方法进行分析。第一个要分析的方法是traverseStreamGraphAndGenerateHashes,它会对StreamGraph进行遍历并为每一个StreamNode都生成其哈希值,生成的哈希值将用于为每个JobVertex创建JobVertexID。方法的完整实现如下:
private Map<Integer, byte[]> traverseStreamGraphAndGenerateHashes() {
//hash函数
final HashFunction hashFunction = Hashing.murmur3_128(0);
final Map<Integer, byte[]> hashes = new HashMap<>();
//存储访问过了的节点编号
Set<Integer> visited = new HashSet<>();
//入队即将访问的节点对象
Queue<StreamNode> remaining = new ArrayDeque<>();
//source是一个流拓扑的起点,从source开始遍历
//hash值的生成是顺序敏感的(依赖于顺序),因此首先要对source ID集合进行排序