接着《Flink源码系列——一个简单的数据处理功能的实现过程》一文的结尾继续分析,在完成对数据流的转换操作之后,需要执行任务,这时会调用如下代码:
env.execute("Socket Window WordCount");
在StreamExecutionEnvironment中,这是一个抽象方法,具体的实现在其子类中,其子类StreamContextEnvironment的实现如下:
public JobExecutionResult execute(String jobName) throws Exception {
Preconditions.checkNotNull("Streaming Job name should not be null.");
StreamGraph streamGraph = this.getStreamGraph();
streamGraph.setJobName(jobName);
transformations.clear();
// execute the programs
if (ctx instanceof DetachedEnvironment) {
LOG.warn("Job was executed in detached mode, the results will be available on completion.");
((DetachedEnvironment) ctx).setDetachedPlan(streamGraph);
return DetachedEnvironment.DetachedJobExecutionResult.INSTANCE;
} else {
return ctx
.getClient()
.run(streamGraph, ctx.getJars(), ctx.getClasspaths(), ctx.getUserCodeClassLoader(), ctx.getSavepointRestoreSettings())
.getJobExecutionResult();
}
}
这里先分析StreamGraph的获取过程,所以这里只看这句代码的执行过程:
StreamGraph streamGraph = this.getStreamGraph();
进一步的调用了父类的方法,实现如下:
public StreamGraph getStreamGraph() {
if (transformations.size() <= 0) {
throw new IllegalStateException("No operators defined in streaming topology. Cannot execute.");
}
return StreamGraphGenerator.generate(this, transformations);
}
这里的transformations这个列表就是在对数据流的处理过程中,会将flatMap、reduce这些转换操作对应的StreamTransformation保存下来的列表,根据对数据流做的转换操作,这个列表中,当前保存的对象有:
a、表示flatMap操作的OneInputTransformation对象,其input属性指向的是数据源的转换SourceTransformation。
b、表示reduce操作的OneInputTransformation对象,其input属性指向的是表示keyBy的转换PartitionTransformation,而PartitionTransformation的input属性指向的是flatMap的转换OneInputTransformation;
c、sink操作对应的SinkTransformation对象,其input属性指向的是reduce转化的OneInputTransformation对象。
接着调用StreamGraphGenerator的generate方法来获取transformations列表对应的StreamGraph。从这里可以看出,数据流在经过各种转换操作之后,各种转换的相关信息都已经保存在了transformations这个列表里的元素中了。继续向下看代码:
public static StreamGraph generate(StreamExecutionEnvironment env, List<StreamTransformation<?>> transformations) {
return new StreamGraphGenerator(env).generateInternal(transformations);
}
可以看到,先以evn为构造函数的入参,构建了一个StreamGraphGenerator实例,看下构造函数的实现:
private StreamGraphGenerator(StreamExecutionEnvironment env) {
this.streamGraph = new StreamGraph(env);
this.streamGraph.setChaining(env.isChainingEnabled());
this.streamGraph.setStateBackend(env.getStateBackend());
this.env = env;
this.alreadyTransformed = new HashMap<>();
}
这个构造函数是private的,所以StreamGraphGenerator的实例构造只能通过其静态的generate方法。另外在构造函数中,初始化了一个StreamGraph实例,并设置了一些属性值,然后给env赋值,并初始化alreadyTransformed为一个空map。再看下StreamGraph的构造函数实现。
public StreamGraph(StreamExecutionEnvironment environment) {
this.environment = environment;
this.executionConfig = environment.getConfig();
this.checkpointConfig = environment.getCheckpointConfig();
/** 构建一个空的新StreamGraph */
clear();
}
public void clear() {
streamNodes = new HashMap<>();
virtualSelectNodes = new HashMap<>();
virtualSideOutputNodes = new HashMap<>();
virtualPartitionNodes = new HashMap<>();
vertexIDtoBrokerID = new HashMap<>();
vertexIDtoLoopTimeout = new HashMap<>();
iterationSourceSinkPairs = new HashSet<>();
sources = new HashSet<>();
sinks = new HashSet<>();
}
可以看出其构造函数中就是做了一些初始化的操作,给StreamGraph的各个属性设置初始值,都是一些空集合。
在获取到StreamGraphGenerator的实例后,继续看其generatorInternal方法的逻辑:
private StreamGraph generateInternal(List<StreamTransformation<?>> transformations) {
for (StreamTransformation<?> transformation: transformations) {
transform(transformation);
}
return streamGraph;
}
逻辑很明了,就是顺序遍历transformations列表中的元素,然后依次转换,最后返回转化好的StreamGraph的实例。
1、flatMap对应StreamTransformation子类实例的转化
接下来就看下,对于transformations列表中的每个元素是如何转化的。
private Collection<Integer> transform(StreamTransformation<?> transform) {
/** 判断传入的transform是否已经被转化过, 如果已经转化过, 则直接返回转化后对应的结果 */
if (alreadyTransformed.containsKey(transform)) {
return alreadyTransformed.get(transform);
}
LOG.debug("Transforming " + transform);
/** 对于该transform, 如果没有设置最大并行度, 则尝试获取job的最大并行度, 并设置给它 */
if (transform.getMaxParallelism() <= 0) {
/** 如果最大并行度没有设置, 如果job设置了最大并行度,则获取job最大并行度 */
int globalMaxParallelismFromConfig = env.getConfig().getMaxParallelism();
if (globalMaxParallelismFromConfig >