OperatorChain设计
创始人
2025-05-31 07:02:15

在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为空:

    • List>的size为1:说明上面创建Output的过程中,只创建了1个Output组件,那就直接return
    • List>的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 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);}
}

相关内容

热门资讯

游戏服务器是什么怎么租用 游戏服务器是什么怎么租用 我是艾西,作为一个常年与游戏行业保持着高频率的服务器供应商&...
Flink-转换算子  基本转换算子         map(映射)         filter(过滤&#...
2023年金三银四大厂高频Ja... Java 面试 谈到 Java 面试,相信大家第一时间脑子里想到的词肯定是金三银四&#...
C语言手撕一个Hash表(Ha... 什么是Hash Table 散列表用的是数组支持按照下标随机访问数据的特性,所以散列表...
springMVC01- 文章目录今日目标一、SpringMVC简介1 SpringMVC概述问题导入1.1 SpringMV...
Electron开发的应用利用... 技术选型: 1、electron:21.3.3 2、electron-v...
【Elastic (ELK) ... 目录 一、ES 基本概念介绍 1.1 ES 是什么 1.2 ES 主要功能 1.3 ES 相关术语 ...
指定wb用户在指定日期范围内的... 一、操作步骤 只记录过程,不讲述原理 1.获取用户ID和cookie 用户ID在进入个...
sheng的学习笔记-IO多路... 基础概念IO分为几种:同步阻塞的BIO,同步非阻塞的NIO,...
接口自动化测试(Python+...  目录:导读 (1)接口自动化测试的优缺点 (2)Pyth...
重构条件-Consolidat... 重构条件-Consolidate Conditional Expression合并条件式二 1.合并...
【论文阅读】BiSeNet V... 前言BiSeNet V2延续了v1版本的双边结构,分别处理空间细节信息、高层语义信息。...
二、马尔可夫决策过程与贝尔曼方... 这里写目录标题1 马尔可夫性质2 马尔可夫过程3 马尔可夫奖励过程(Markov re...
golang端口重用 文章目录前言SO_REUSEADDR简介Python中的用法golang用法其他学习总结 前言 服...
Zabbix“专家坐诊”第18... 问题一 Q:Zabbix5.0版本,如图,请问这里怎么修改...
深度学习技巧应用5-神经网络中... 大家好,我是微学AI,今天给大家带来深度学习技巧应用5-神经网络中的模型...
Mongodb 常用基本语法与... 常用操作 1、 Help查看命令提示 db.help(); 2、 切换/创建数据库 use t...
java中Long型数据大小比... 起因 今天在做项目的时候,想构建一个树形结构,从数据库中查询出了所有数据...
【Linux】-- 进程概念 基本概念进程(Process):是操作系统进行资源分配的最小单位。一个进程是一个程序的一次执行过程。...
2023-03-22干活小计: transformer: position-embedding: 残差:我也会了 ad...
verilog(基础知识) 摘要:主要写自己的学习内容,可能不完整 概述 对硬件描述,主要是对芯片设计进行验证人员对其进行验证...
MySQL函数 - 字符串函数... 文章目录1 字符串函数2 数值函数3 日期函数4 流程函数 函数是指一段可以直接被另一段程序调用的程...
Word2010(详细布局解释... 目录一、界面介绍二、选项卡1、文件选项卡(保存、打开、新建、打印、保存并发送、选项&#...
ProTradex是链上衍生品... 目前,链上衍生品市场的总市值已经超过100亿美元,链上衍生品市场的产品类...
spring boot 集成 ... 要将 PostGIS 集成到 Spring Boot 应用程序中,需要按照以下步骤进行操作:1. 将...
【DDIM精读】公式推导加代码... 【DDIM精读】公式推导加代码分析。1.前言:ddim总览2.均值(μ\...
系统开发-McCabe复杂度(... 系统开发(上)-软件设计(三十二)https...
每日学术速递3.22 CV - 计算机视觉 |  ML - 机器学习 |  RL - 强化学习 | NLP 自然语言处理 ...
CCF-CSP题解 第二题(J... 目录 201312-2:ISBN号码 201403-2:窗口 20140...
在服务器上搭建nacos集群-... 搭建集群需要具备JDK环境,1个Nginx+3个nacos注册中心+1...