OperatorChain设计

文章详细介绍了Flink中OperatorChain的初始化过程,包括如何构建RecordWriterOutput组件来处理数据输出,以及StreamOperator和ChainingOutput组件在数据传输中的作用。OperatorChain通过连接满足条件的StreamOperator在同一线程中运行,数据通过Output组件在StreamOperator间流转,最终由RecordWriterOutput序列化并发送到网络,供下游Task使用。
摘要由CSDN通过智能技术生成

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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值