在JobGraph构建过程中,会将满足链化条件的StreamOperator连接在一起,形成OperatorChain。OperatorChain中的所有StreamOperator都会运行在同一个Task线程中。在数据接入处理时,会利用StreamTaskNetworkOutput将StreamRecord传递给OperatorChain中的HeadOperator处理。在OperatorChain内部,上游StreamOperator会通过Output组件将StreamRecord传递给下游StreamOperator。
/*** OperatorChain构造函数:OperatorChain创建完成后,就能正常接收StreamTaskInput中的数据元素了,在OperatorChain的内部算子中进行数据传递、处理* 最终经过RecordWriterOutput组件将处理完成的数据发送到网络中,供下游Task实例使用*/
public OperatorChain(StreamTask containingTask,RecordWriterDelegate>> recordWriterDelegate) {// 获取StreamTask的类加载器ClassLoader和StreamConfigfinal ClassLoader userCodeClassloader = containingTask.getUserCodeClassLoader();final StreamConfig configuration = containingTask.getConfiguration();// 在使用DataStream API进行转换时,会将生成的StreamOperator包装到StreamOperatorFactory中StreamOperatorFactory operatorFactory = configuration.getStreamOperatorFactory(userCodeClassloader);// we read the chained configs, and the order of record writer registrations by output name// OperatorChain的链化配置,决定了各算子之间的Output组件的具体实现是哪个Map chainedConfigs = configuration.getTransitiveChainedTaskConfigsWithSelf(userCodeClassloader);// 当前作业的所有JobVertex节点的所有输出边,会被用来创建对应数量的RecordWriterOutputList outEdgesInOrder = configuration.getOutEdgesInOrder(userCodeClassloader);// 按照“StreamEdge:RecordWriterOutput”的映射关系存放到Map集合中,1个输出边对应1个RecordWriterOutput(写入到网络)Map> streamOutputMap = new HashMap<>(outEdgesInOrder.size());// 构建定长的RecordWriterOutput[]数组,用来容纳等同于输出边数量的RecordWriterOutputthis.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> allOps = new ArrayList<>(chainedConfigs.size());/*** 以递归调用的方式,确定OperatorChain内各StreamOperator之间进行数据传输的的WatermarkGaugeExposingOutput到底是哪个*/this.chainEntryPoint = createOutputCollector(containingTask,configuration,chainedConfigs,userCodeClassloader,streamOutputMap,allOps,containingTask.getMailboxExecutorFactory());if (operatorFactory != null) {// 获取刚刚创建好的Output组件WatermarkGaugeExposingOutput> 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组件。
首先,看这个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会按照“StreamEdge:RecordWriterOutput”的映射关系保存到Map集合中。最后会经过综合考虑后,对其进行处理。
接着会以递归调用的方式,确定OperatorChain内各StreamOperator之间进行数据传输的Output组件到底是哪个。
/*** 以递归调用的方式,确定OperatorChain内各StreamOperator之间进行数据传输的Output组件到底是哪个*/
private WatermarkGaugeExposingOutput> createOutputCollector(StreamTask, ?> containingTask,StreamConfig operatorConfig,Map chainedConfigs,ClassLoader userCodeClassloader,Map> streamOutputs,List> allOperators,MailboxExecutorFactory mailboxExecutorFactory) {// 专门容纳WatermarkGaugeExposingOutput的集合:一共就4大类List>, 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 output = (RecordWriterOutput) streamOutputs.get(outputEdge);// 将RecordWriterOutput和输出边,包装成Tuple2后,保存到List集合中allOutputs.add(new Tuple2<>(output, outputEdge));}/*** 遍历可以链化的StreamEdge,创建Output组件,用于在OperatorChain内的各StreamOperator之间传输数据。*/for (StreamEdge outputEdge : operatorConfig.getChainedOutputs(userCodeClassloader)) {// StreamEdge连接的下游StreamNode的唯一IDint outputId = outputEdge.getTargetId();StreamConfig chainedOpConfig = chainedConfigs.get(outputId);/*** 开始递归,递归过程中会创建OperatorChain中链化的各个StreamOperator以及对应的(上下游算子之间传输数据的)Output组件。* 此时得到的Output,一定是(上游算子为单输出类型的)ChainingOutput 或 CopyingChainingOutput*/WatermarkGaugeExposingOutput> 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> selectors = operatorConfig.getOutputSelectors(userCodeClassloader);/*** 如果上下游算子之间的Selector为空,就视情况包装BroadcastingOutputCollector、CopyingBroadcastingOutputCollector;* 反之,就视情况包装DirectedOutputCollector、CopyingDirectedOutputCollector。*/if (selectors == null || selectors.isEmpty()) {// 如果上面只创建了1个WatermarkGaugeExposingOutput,那就直接取出并returnif (allOutputs.size() == 1) {return allOutputs.get(0).f0;}// 如果上面创建了N个WatermarkGaugeExposingOutput,那就该视情况包装BroadcastingOutputCollector或CopyingBroadcastingOutputCollectorelse {@SuppressWarnings({"unchecked", "rawtypes"})Output>[] asArray = new Output[allOutputs.size()];for (int i = 0; i < allOutputs.size(); i++) {// 如果上面创建了N个WatermarkGaugeExposingOutput,那就将它们都包装到数组中,用来构建多输出类型的WatermarkGaugeExposingOutputasArray[i] = allOutputs.get(i).f0;}if (containingTask.getExecutionConfig().isObjectReuseEnabled()) {return new CopyingBroadcastingOutputCollector<>(asArray, this);} else {return new BroadcastingOutputCollector<>(asArray, this);}}}else {// 如果有Selector,那就意味着需要创建适用于多输出类型的DirectedOutput或CopyingDirectedOutputif (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 WatermarkGaugeExposingOutput> createChainedOperator(StreamTask containingTask,StreamConfig operatorConfig,Map chainedConfigs,ClassLoader userCodeClassloader,Map> streamOutputs,List> allOperators,OutputTag outputTag,MailboxExecutorFactory mailboxExecutorFactory) {// 递归创建Output组件WatermarkGaugeExposingOutput> chainedOperatorOutput = createOutputCollector(containingTask,operatorConfig,chainedConfigs,userCodeClassloader,streamOutputs,allOperators,mailboxExecutorFactory);// 创建StreamOperator,并为其提供Output组件OneInputStreamOperator chainedOperator = StreamOperatorFactoryUtil.createOperator(operatorConfig.getStreamOperatorFactory(userCodeClassloader),containingTask,operatorConfig,chainedOperatorOutput);// 将创建好的StreamOperator添加到List集合中allOperators.add(chainedOperator);// 确定Output组件的具体类型(上游算子为单输出类型)WatermarkGaugeExposingOutput> currentOperatorOutput;if (containingTask.getExecutionConfig().isObjectReuseEnabled()) {currentOperatorOutput = new ChainingOutput<>(chainedOperator, this, outputTag);}else {TypeSerializer 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为空:
如果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 record) {if (this.outputTag != null) {return;}// 将数据写入RecordWriter组件中pushToRecordWriter(record);
}/*** 将数据写入到RecordWriter组件中,由RecordWriter将数据写出到网络中*/
private void pushToRecordWriter(StreamRecord record) {// 将接入的StreamRecord进行序列化,成二进制格式serializationDelegate.setInstance(record);try {// 通过RecordWriter组件将二进制的数据输出到下游网络中recordWriter.emit(serializationDelegate);}catch (Exception e) {throw new RuntimeException(e.getMessage(), e);}
}