Flink之StreamGraph生成源码分析
一、StreamGraph是什么?
是用户提交的代码,直接生产的数据流图
二、生成StreamGraph的过程
2.1 触发执行生成StreamGraph
程序执行即env.execute("Java WordCount")
这行代码,这里以RemoteStreamEnvironment
环境为例
@Override
public JobExecutionResult execute(String jobName) throws ProgramInvocationException {
StreamGraph streamGraph = getStreamGraph();
streamGraph.setJobName(jobName);
transformations.clear();
return executeRemotely(streamGraph, jarFiles);
}
在StreamGraph streamGraph = getStreamGraph();
将生成StreamGraph,进入该方法发现
public StreamGraph getStreamGraph() {
if (transformations.size() <= 0) {
throw new IllegalStateException("No operators defined in streaming topology. Cannot execute.");
}
return StreamGraphGenerator.generate(this, transformations);
}
StreamGraph是由StreamGraphGenerator.generate(this, transformations)
实现,进而是该类的generateInternal
方法实现
private StreamGraph generateInternal(List<StreamTransformation<?>> transformations) {
for (StreamTransformation<?> transformation: transformations) {
transform(transformation);
}
return streamGraph;
}
最终根据transform(transformation);
生成StreamGraph
Collection<Integer> transformedIds;
if (transform instanceof OneInputTransformation<?, ?>) {
transformedIds = transformOneInputTransform((OneInputTransformation<?, ?>) transform);
} else if (transform instanceof TwoInputTransformation<?, ?, ?>) {
transformedIds = transformTwoInputTransform((TwoInputTransformation<?, ?, ?>) transform);
} else if (transform instanceof SourceTransformation<?>) {
transformedIds = transformSource((SourceTransformation<?>) transform);
}
...
else {
throw new IllegalStateException("Unknown transformation: " + transform);
}
...
if (transform.getUserProvidedNodeHash() != null) {
streamGraph.setTransformationUserHash(transform.getId(), transform.getUserProvidedNodeHash());
}
if (transform.getMinResources() != null && transform.getPreferredResources() != null) {
streamGraph.setResources(transform.getId(), transform.getMinResources(), transform.getPreferredResources());
}
2.2 transformation集合的初始化
我们看到transform(transformation);
生成StreamGraph的参数是transformation,那么该值是什么?在何时赋值的呢?通过追溯代码StreamGraphGenerator.generate(this, transformations)来自环境类,是该属性protected final List<StreamTransformation<?>> transformations = new ArrayList<>();
现在研究下该属性怎么赋值的,在我们的各个算子的代码中,例如env.addSource().map().filter().sink()
,这里已filter为例,在DataStream类型中
public SingleOutputStreamOperator<T> filter(FilterFunction<T> filter) {
return transform("Filter", getType(), new StreamFilter<>(clean(filter)));
}
在该方法的transform
中getExecutionEnvironment().addOperator(resultTransform);
public <R> SingleOutputStreamOperator<R> transform(String operatorName, TypeInformation<R> outTypeInfo, OneInputStreamOperator<T, R> operator) {
OneInputTransformation<T, R> resultTransform = new OneInputTransformation<>(
this.transformation,
operatorName,
operator,
outTypeInfo,
environment.getParallelism());
...
getExecutionEnvironment().addOperator(resultTransform);
return returnStream;
}
把算子添加到transformations完成赋值
2.3 怎么构建StreamGraph
也就是梳理StreamGraphGenerator.transform()
的过程。这里以transformOneInputTransform(OneInputTransformation<IN, OUT> transform)
为例
首先Collection<Integer> inputIds = transform(transform.getInput());
递归对该算子的直接上游算子转换,获取直接上游的id集合,递归结束的条件是alreadyTransformed.containsKey(transform)
再次:String slotSharingGroup = determineSlotSharingGroup(transform.getSlotSharingGroup(), inputIds);
分配共享算子slot
然后在StreamGraph构建节点,最终加入节点集合。
streamGraph.addOperator(transform.getId(), slotSharingGroup, transform.getCoLocationGroupKey(), transform.getOperator(), transform.getInputType(), transform.getOutputType(), transform.getName());
最后在StreamGraph构建边
for (Integer inputId: inputIds) {
streamGraph.addEdge(inputId, transform.getId(), 0);
}
然后调用addEdgeInternal
方法,无论是虚拟节点还是分区节点,都会执行
StreamEdge edge = new StreamEdge(upstreamNode, downstreamNode, typeNumber, outputNames, partitioner, outputTag);
getStreamNode(edge.getSourceId()).addOutEdge(edge);
getStreamNode(edge.getTargetId()).addInEdge(edge);
注意:
- edge.getSourceId()就是new StreamEdge的upstreamNode的id
- edge.getTargetId()就是new StreamEdge的downstreamNode的id
StreamEdge通过sourceId 和targetId属性可以获取边的两个节点。
- addOutEdge是获取节点的输出边集合
- addInEdge(edge)是获取节点的输入边集合。
就是找到该边属于哪个节点的输入边,属于哪个节点的输出边。
StreamNode通过inEdges和outEdges可以获取连接节点的边集合
- 另外downstreamNode是本节点,upstreamNode是本节点的上游节点。
通过节点可以知道节点的边,通过边可以找到边的两个节点,所以就以完整的构建成StreamGraph图。
可以通过
env.getExecutionPlan()
获取StreamGraph的字符串表示形式。
三、涉及几个关键类说明
3.1 StreamTransformation
是所有算子的转换类的接口,每一个DataStream都有一个与之对应的StreamTransformation。它是逻辑上的概念和运行时无关。
3.1.1 主要属性
- name:转换器名字
- uid:用户指定uid
- outputType:输出类型
- slotSharingGroup:设置slot共享组,虚拟节点没有该值
3.1.2 核心抽象方法
- setChainingStrategy:设置算子链策略
- getTransitivePredecessors:返回前置算子,如果没有特别说明,getTransitivePredecessors的实现逻辑都是,由自身加input(上游StreamTransformation)组成的集合
3.1.3 实现的子类
- SourceTransformation:表示source,不做任何的转换操作,是项目的根,无input属性
- SinkTransformation:表示sink操作算子
- OneInputTransformation:表示一个输入流的算子
- TwoInputTransformation:表示两个输入流的算子
- SplitTransformation:分拆流算子
- SelectTransformation:选择算子
- UnionTransformation:合并算子
- PartitionTransformation:分区流算子
- FeedbackTransformation
- CoFeedbackTransformation
他们都有个input属性,是他们直接上游的算子
3.2 StreamGraph
StreamGraph是用户代码直接生成拓扑结果的图表示,表示各个算子的组合方式,是一个DAG图。
3.2.1 主要属性
- Map<Integer, StreamNode> streamNodes:节点信息
- Set sources:数据源节点集合
- Set sinks:下沉节点集合
- Map<Integer, Tuple2<Integer, List>> virtualSelectNodes:虚拟select节点集合
- Map<Integer, Tuple2<Integer, OutputTag>> virtualSideOutputNodes:虚拟sideoutput节点集合
- Map<Integer, Tuple2<Integer, StreamPartitioner<?>>> virtualPartitionNodes:分区节点集合
虚拟节点不会生成真正的节点,会连接上游的非虚拟节点
3.2.2 核心方法
- addOperator:构建streamNodes集合
- addEdge:构建边
- addEdgeInternal:构建边,在该方法中,决定分区的策略,如果没有指定分区则按照上游和下游算子的并行度是否相同决定是本地分发,还是均匀分发
- getJobGraph:生成JobGraph
- getStreamingPlanAsJSON:StreamGraph字符串表示形式