1.OperatorChain初始化
在JobGraph构建过程中,会将满足链化条件的StreamOperator连接在一起,形成OperatorChain。OperatorChain中的所有StreamOperator都会运行在同一个Task线程中。在数据接入处理时,会利用StreamTaskNetworkOutput将StreamRecord传递给OperatorChain中的HeadOperator处理。在OperatorChain内部,上游StreamOperator会通过Output组件将StreamRecord传递给下游StreamOperator。
/**
* OperatorChain构造函数:OperatorChain创建完成后,就能正常接收StreamTaskInput中的数据元素了,在OperatorChain的内部算子中进行数据传递、处理
* 最终经过RecordWriterOutput组件将处理完成的数据发送到网络中,供下游Task实例使用
*/
public OperatorChain(
StreamTask<OUT, OP> containingTask,
RecordWriterDelegate<SerializationDelegate<StreamRecord<OUT>>> recordWriterDelegate) {
// 获取StreamTask的类加载器ClassLoader和StreamConfig
final ClassLoader userCodeClassloader = containingTask.getUserCodeClassLoader();
final StreamConfig configuration = containingTask.getConfiguration();
// 在使用DataStream API进行转换时,会将生成的StreamOperator包装到StreamOperatorFactory中
StreamOperatorFactory<OUT> operatorFactory = configuration.getStreamOperatorFactory(userCodeClassloader);
// we read the chained configs, and the order of record writer registrations by output name
// OperatorChain的链化配置,决定了各算子之间的Output组件的具体实现是哪个
Map<Integer, StreamConfig> chainedConfigs = configuration.getTransitiveChainedTaskConfigsWithSelf(userCodeClassloader);
// 当前作业的所有JobVertex节点的所有输出边,会被用来创建对应数量的RecordWriterOutput
List<StreamEdge> outEdgesInOrder = configuration.getOutEdgesInOrder(userCodeClassloader);
// 按照“StreamEdge:RecordWriterOutput”的映射关系存放到Map集合中,1个输出边对应1个RecordWriterOutput(写入到网络)
Map<StreamEdge, RecordWriterOutput<?>> streamOutputMap = new HashMap<>(outEdgesInOrder.size());
// 构建定长的RecordWriterOutput[]数组,用来容纳等同于输出边数量的RecordWriterOutput
this.streamOutputs = new RecordWriterOutput<?>[outEdgesInOrder.size()];
boolean success = false;
try {
/**
* 根据当前OperatorChain的最终输出边数量,按顺序构建对应的RecordWriterOutput。通过RecordWriterOutput组件(内部借助RecordWriter组件),
* 可以将OperatorChain的尾部算子处理过的数据元素输出到网络中(OperatorChain ---> 下一个Task实例)
*/
for (int i = 0; i < outEdgesInOrder.size(); i++) {
// 遍历到1个输出边
StreamEdge outEdge = outEdgesInOrder.get(i);
// 为这个StreamEdge(输出边)创建RecordWriterOutput组件
RecordWriterOutput<?> streamOutput = createStreamOutput(
recordWriterDelegate.getRecordWriter(i),
outEdge,
chainedConfigs.get(outEdge.getSourceId()),
containingTask.getEnvironment());
// 将创建好的RecordWriterOutput,添加到RecordWriterOutput[]数组的对应Index上
this.streamOutputs[i] = streamOutput;
// 按照“StreamEdge:RecordWriterOutput”的映射关系存放到Map集合中
streamOutputMap.put(outEdge, streamOutput);
}
// 根据OperatorChain内链化的节点数量,创建一个指定容量大小的List集合
List<StreamOperator<?>> allOps = new ArrayList<>(chainedConfigs.size());
/**
* 以递归调用的方式,确定OperatorChain内各StreamOperator之间进行数据传输的的WatermarkGaugeExposingOutput到底是哪个
*/
this.chainEntryPoint = createOutputCollector(
containingTask,
configuration,
chainedConfigs,
userCodeClassloader,
streamOutputMap,
allOps,
containingTask.getMailboxExecutorFactory());
if (operatorFactory != null) {
// 获取刚刚创建好的Output组件
WatermarkGaugeExposingOutput<StreamRecord<OUT>> output = getChainEntryPoint();
// 利用StreamOperatorFactory单独创建OperatorChain中的HeadOperator(调用转换方法时生成的StreamOperator会包装到StreamOperatorFactory中),
// HeadOperator会被暴露给StreamTask实例,让外部数据可以通过DataOutput写入到OperatorChain的HeadOperator中
headOperator = StreamOperatorFactoryUtil.createOperator(
operatorFactory,
containingTask,
configuration,
output);
headOperator.getMetricGroup().gauge(MetricNames.IO_CURRENT_OUTPUT_WATERMARK, output.getWatermarkGauge());
} else {
headOperator = null;
}
// 最后添加HeadOperator至尾部(因为会以相反的顺序存储)
allOps.add(headOperator);
// 以相反的顺序(因为递归创建Output组件时,顺带着创建了StreamOperator并add到了List集合),存储该链上的所有操作符
this.allOperators = allOps.toArray(new StreamOperator<?>[allOps.size()]);
success = true;
}
finally {
// 如果创建OperatorChain失败,就关闭(刚刚拿到的)RecordWriterOutput,防止出现内存泄漏
if (!success) {
for (RecordWriterOutput<?> output : this.streamOutputs) {
if (output != null) {
output.close();
}
}
}
}
}
在OperatorChain的构造方法中,最核心的就是构建对应类型的Output组件。
2.构建RecordWriterOutput组件
首先,看这个OperatorChain有几个输出边,就会创建几个RecordWriterOutput。RecordWriterOutput(内部借助RecordWriter组件)可以将OperatorChain内的尾部算子处理过的数据写入到网络中,交给下一个Task。
/**
* 根据当前OperatorChain的最终输出边数量,按顺序构建对应的RecordWriterOutput。通过RecordWriterOutput组件(内部借助RecordWriter组件),
* 可以将OperatorChain的尾部算子处理过的数据元素输出到网络中(OperatorChain ---> 下一个Task实例)
*/
for (int i = 0; i < outEdgesInOrder.size(); i++) {
// 遍历到1个输出边
StreamEdge outEdge = outEdgesInOrder.get(i);
// 为这个StreamEdge(输出边)创建RecordWriterOutput组件
RecordWriterOutput<?> streamOutput = createStreamOutput(
recordWriterDelegate.getRecordWriter(i),
outEdge,
chainedConfigs.get(outEdge.getSourceId()),
containingTask.getEnvironment());
// 将创建好的RecordWriterOutput,添加到RecordWriterOutput[]数组的对应Index上
this.streamOutputs[i] = streamOutput;
// 按照“StreamEdge:RecordWriterOutput”的映射关系存放到Map集合中
streamOutputMap.put(outEdge, streamOutput);
}
构建RecordWriterOutput的核心逻辑如下:
/**
* 创建RecordWriterOutput组件:(内部借助RecordWriter组件)将OperatorChain的尾部算子处理完的数据,写入网络(交给下一个Task实例)
*/
private RecordWriterOutput<OUT> createStreamOutput(
RecordWriter<SerializationDelegate<StreamRecord<OUT>>> recordWriter,
StreamEdge edge,
StreamConfig upStreamConfig,
Environment taskEnvironment) {
// 获取当前OperatorChain的这个最终输出边所对应的OutputTag标签(用来判断这个StreamEdge是否为旁路输出,即DataStream API中是否使用了侧输出流)
OutputTag sideOutputTag = edge.getOutputTag(); // OutputTag, return null if not sideOutput
// 确定这个最终StreamEdge对应的TypeSerializer是哪个
TypeSerializer outSerializer = null;
// 如果当前OperatorChain使用了侧输出流
if (edge.getOutputTag() != null) {
// side output
// 获取OutputTag对应的TypeSerializer
outSerializer = upStreamConfig.getTypeSerializerSideOut(
edge.getOutputTag(), taskEnvironment.getUserClassLoader());
} else {
// 如果当前OperatorChain没有使用侧输出流,只是进行正常输出
outSerializer = upStreamConfig.getTypeSerializerOut(taskEnvironment.getUserClassLoader());
}
// 根据确定好的RecordWriter、TypeSerializer等,创建RecordWriterOutput实例并返回
return new RecordWriterOutput<>(recordWriter, outSerializer, sideOutputTag, this);
}
判断当前这个OperatorChain的“最终输出边”是否为旁路输出,对应不同的TypeSerializer类型序列化器。这样做是为了让“目前还在OperatorChain中处理”的StreamRecord以BufferOrEvent的形式输出到网络中。
3.构建StreamOperator和ChainingOutput组件
创建好的RecordWriterOutput会按照“StreamEdge:RecordWriterOutput”的映射关系保存到Map集合中。最后会经过综合考虑后,对其进行处理。
接着会以递归调用的方式,确定OperatorChain内各StreamOperator之间进行数据传输的Output组件到底是哪个。
/**
* 以递归调用的方式,确定OperatorChain内各StreamOperator之间进行数据传输的Output组件到底是哪个
*/
private <T> WatermarkGaugeExposingOutput<StreamRecord<T>> createOutputCollector(
StreamTask<?, ?> containingTask,
StreamConfig operatorConfig,
Map<Integer, StreamConfig> chainedConfigs,
ClassLoader userCodeClassloader,
Map<StreamEdge, RecordWriterOutput<?>> streamOutputs,
List<StreamOperator<?>> allOperators,
MailboxExecutorFactory mailboxExecutorFactory) {
// 专门容纳WatermarkGaugeExposingOutput的集合:一共就4大类
List<Tuple2<WatermarkGaugeExposingOutput<StreamRecord<T>>, StreamEdge>> allOutputs = new ArrayList<>(4);
/**
* 遍历不能链化的StreamEdge,将早已准备好的RecordWriterOutput,从保存它的Map集合中取出来,
* 将其连同StreamEdge一起包装成Tuple2后,add到List集合中
*/
for (StreamEdge outputEdge : operatorConfig.getNonChainedOutputs(userCodeClassloader)) {
// 根据输出边,从映射关系为“StreamEdge:RecordWriterOutput”的Map集合中(保存了输出边对应的RecordWriterOutput),取出对应的RecordWriterOutput
@SuppressWarnings("unchecked")
RecordWriterOutput<T> output = (RecordWriterOutput<T>) streamOutputs.get(outputEdge);
// 将RecordWriterOutput和输出边,包装成Tuple2后,保存到List集合中
allOutputs.add(new Tuple2<>(output, outputEdge));
}
/**
* 遍历可以链化的StreamEdge,创建Output组件,用于在OperatorChain内的各StreamOperator之间传输数据。
*/
for (StreamEdge outputEdge : operatorConfig.getChainedOutputs(userCodeClassloader)) {
// StreamEdge连接的下游StreamNode的唯一ID
int outputId = outputEdge.getTargetId();
StreamConfig chainedOpConfig = chainedConfigs.get(outputId);
/**
* 开始递归,递归过程中会创建OperatorChain中链化的各个StreamOperator以及对应的(上下游算子之间传输数据的)Output组件。
* 此时得到的Output,一定是(上游算子为单输出类型的)ChainingOutput 或 CopyingChainingOutput
*/
WatermarkGaugeExposingOutput<StreamRecord<T>> output = createChainedOperator(
containingTask,
chainedOpConfig,
chainedConfigs,
userCodeClassloader,
streamOutputs,
allOperators,
outputEdge.getOutputTag(),
mailboxExecutorFactory);
// 将(上游算子为单输出类型的)Output和StreamEdge包装成Tuple2后,添加到List集合中
allOutputs.add(new Tuple2<>(output, outputEdge));
}
/**
* 上面已经确认好了当前OperatorChain所需要的所有的Output,现在就要根据输出的数量,
* 确定到底用哪个子类型的WatermarkGaugeExposingOutput(也就是通俗理解的Output组件),
* 视情况,直接返回(上游算子为单输出类型的)Output,还是继续将“单输出”的Output进一步包装成支持“多输出”的Output并返回。
*/
List<OutputSelector<T>> selectors = operatorConfig.getOutputSelectors(userCodeClassloader);
/**
* 如果上下游算子之间的Selector为空,就视情况包装BroadcastingOutputCollector、CopyingBroadcastingOutputCollector;
* 反之,就视情况包装DirectedOutputCollector、CopyingDirectedOutputCollector。
*/
if (selectors == null || selectors.isEmpty()) {
// 如果上面只创建了1个WatermarkGaugeExposingOutput,那就直接取出并return
if (allOutputs.size() == 1) {
return allOutputs.get(0).f0;
}
// 如果上面创建了N个WatermarkGaugeExposingOutput,那就该视情况包装BroadcastingOutputCollector或CopyingBroadcastingOutputCollector
else {
@SuppressWarnings({"unchecked", "rawtypes"})
Output<StreamRecord<T>>[] asArray = new Output[allOutputs.size()];
for (int i = 0; i < allOutputs.size(); i++) {
// 如果上面创建了N个WatermarkGaugeExposingOutput,那就将它们都包装到数组中,用来构建多输出类型的WatermarkGaugeExposingOutput
asArray[i] = allOutputs.get(i).f0;
}
if (containingTask.getExecutionConfig().isObjectReuseEnabled()) {
return new CopyingBroadcastingOutputCollector<>(asArray, this);
} else {
return new BroadcastingOutputCollector<>(asArray, this);
}
}
}
else {
// 如果有Selector,那就意味着需要创建适用于多输出类型的DirectedOutput或CopyingDirectedOutput
if (containingTask.getExecutionConfig().isObjectReuseEnabled()) {
return new CopyingDirectedOutput<>(selectors, allOutputs);
} else {
return new DirectedOutput<>(selectors, allOutputs);
}
}
}
针对不能链化的StreamEdge,说明这个StreamEdge是连接2个OperatorChain的。于是就把刚刚创建好的RecordWriterOutput和对应的StreamEdge一并包装成Tuple2后,添加到List集合中。
针对可以链化的StreamEdge,那就以递归的方式创建OperatorChain内部各个StreamOperator之间用到的Output组件:
/**
* 开始递归,递归过程中会创建OperatorChain中链化的各个StreamOperator(除HeadOperator以外)
* 以及对应的(上下游算子之间传输数据的、上游算子为单输出类型的)Output组件。
*/
private <IN, OUT> WatermarkGaugeExposingOutput<StreamRecord<IN>> createChainedOperator(
StreamTask<OUT, ?> containingTask,
StreamConfig operatorConfig,
Map<Integer, StreamConfig> chainedConfigs,
ClassLoader userCodeClassloader,
Map<StreamEdge, RecordWriterOutput<?>> streamOutputs,
List<StreamOperator<?>> allOperators,
OutputTag<IN> outputTag,
MailboxExecutorFactory mailboxExecutorFactory) {
// 递归创建Output组件
WatermarkGaugeExposingOutput<StreamRecord<OUT>> chainedOperatorOutput = createOutputCollector(
containingTask,
operatorConfig,
chainedConfigs,
userCodeClassloader,
streamOutputs,
allOperators,
mailboxExecutorFactory);
// 创建StreamOperator,并为其提供Output组件
OneInputStreamOperator<IN, OUT> chainedOperator = StreamOperatorFactoryUtil.createOperator(
operatorConfig.getStreamOperatorFactory(userCodeClassloader),
containingTask,
operatorConfig,
chainedOperatorOutput);
// 将创建好的StreamOperator添加到List集合中
allOperators.add(chainedOperator);
// 确定Output组件的具体类型(上游算子为单输出类型)
WatermarkGaugeExposingOutput<StreamRecord<IN>> currentOperatorOutput;
if (containingTask.getExecutionConfig().isObjectReuseEnabled()) {
currentOperatorOutput = new ChainingOutput<>(chainedOperator, this, outputTag);
}
else {
TypeSerializer<IN> inSerializer = operatorConfig.getTypeSerializerIn1(userCodeClassloader);
currentOperatorOutput = new CopyingChainingOutput<>(chainedOperator, inSerializer, outputTag, this);
}
chainedOperator.getMetricGroup().gauge(MetricNames.IO_CURRENT_INPUT_WATERMARK, currentOperatorOutput.getWatermarkGauge()::getValue);
chainedOperator.getMetricGroup().gauge(MetricNames.IO_CURRENT_OUTPUT_WATERMARK, chainedOperatorOutput.getWatermarkGauge()::getValue);
// return创建好的(上游算子为单输出类型的)Output组件
return currentOperatorOutput;
}
在递归调用过程中,会捎带着一并创建好OperatorChain内的StreamOperator(HeadOperator除外,它会在最后单独添加),并保存到List集合中。在递归的最底层,会创建(上游算子为单输出类型的)ChainingOutput或CopyingChainingOutput,并会被像RecordWriterOutput一样,和对应的StreamEdge一并包装成Tuple2后,添加到List集合中。
接下来,就要根据上下游算子之间的Selector是否为空,决定是否基于“单输出类型”的Output,包装“支持多输出类型”的Output组件了:
-
如果Selector为空:
- List<Tuple2<WatermarkGaugeExposingOutput, StreamEdge>>的size为1:说明上面创建Output的过程中,只创建了1个Output组件,那就直接return
- List<Tuple2<WatermarkGaugeExposingOutput, StreamEdge>>的size大于1:说明需要基于“单输出类型”的Output组件,包装“多输出类型”的Outout组件,BroadcastingOutputCollector or CopyingBroadcastingOutputCollector
-
如果Selector不为空:需要基于“单输出类型”的Output组件,包装成“多输出类型”的DirectedOutput or CopyingDirectedOutput

搞定所有的Output组件后,OperatorChain内除HeadOperator以外的所有StreamOperator也都已经保存好了。最后单独创建好HeadOperator一并添加到List集合中保存,然后将List集合转换为StreamOperator[]数组。
OperatorChain准备好后,HeadOperator就能正常接收StreamTaskInput的数据处理了。并且在OperatorChain内部,数据通过Output组件在各个StreamOperator中间流转。最后由RecordWriterOutput组件,将尾部算子处理过的数据写入到网络,交给下一个OperatorChain。
RecordWriterOutput内提供了“发送至下游网络”的具体实现逻辑,本质就是将数据序列化成二进制,利用RecordWriter组件输出到下游网络中。
/**
* 定义了StreamRecord的输出逻辑,由RecordWriter组件实现
*/
@Override
public void collect(StreamRecord<OUT> record) {
if (this.outputTag != null) {
return;
}
// 将数据写入RecordWriter组件中
pushToRecordWriter(record);
}
/**
* 将数据写入到RecordWriter组件中,由RecordWriter将数据写出到网络中
*/
private <X> void pushToRecordWriter(StreamRecord<X> record) {
// 将接入的StreamRecord进行序列化,成二进制格式
serializationDelegate.setInstance(record);
try {
// 通过RecordWriter组件将二进制的数据输出到下游网络中
recordWriter.emit(serializationDelegate);
}
catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
文章详细介绍了Flink中OperatorChain的初始化过程,包括如何构建RecordWriterOutput组件来处理数据输出,以及StreamOperator和ChainingOutput组件在数据传输中的作用。OperatorChain通过连接满足条件的StreamOperator在同一线程中运行,数据通过Output组件在StreamOperator间流转,最终由RecordWriterOutput序列化并发送到网络,供下游Task使用。
555

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



