【Flink网络数据传输(4)】RecordWriter(下)封装数据并发送到网络的过程

文章目录

  • 一. RecordWriter封装数据并发送到网络
    • 1. 数据发送到网络的具体流程
    • 2. 源码层面
      • 2.1. Serializer的实现逻辑
        • a. SpanningRecordSerializer的实现
        • b. SpanningRecordSerializer中如何对数据元素进行序列化
      • 2.2. 将ByteBuffer中间数据写入BufferBuilder
  • 二. BufferBuilder申请资源并创建
    • 1. ChannelSelectorRecordWriter创建BufferBuilder
    • 2. BroadcastRecordWriter创建BufferBuilder

一. RecordWriter封装数据并发送到网络

1. 数据发送到网络的具体流程

RecordWriter对接入的StreamRecord数据进行序列化并等待下游任务消费的过程,整个过程细节如下。

  1. StreamRecord通过RecordWriterOutput写入RecordWriter,并在RecordWriter中通过RecordSerializer组件将StreamRecord序列化为ByteBuffer数据格式。

  2. RecordWriter向ResultPartition申请BufferBuilder对象,用于构建BufferConsumer对象,将序列化后的二进制数据存储在申请到的Buffer中。ResultPartition会向LocalBufferPool申请MemorySegment内存块,用于存储Buffer数据

  3. BufferBuilder中会不断接入ByteBuffer数据,直到将BufferBuilder中的Buffer空间占满,此时会申请新的BufferBuilder继续构建BufferConsumer数据集。

  4. Buffer构建完成后,会调用flushTargetPartition()方法,让ResultPartition向下游输出数据,此时会通知NetworkSequenceViewReader组件开始消费ResultSubPartition中的BufferConsumer对象。

  5. 当BufferConsumer中Buffer数据被推送到网络后,回收BufferConsumer中的MemorySegment内存空间,继续用于后续的消息处理。

在这里插入图片描述

 

2. 源码层面

接下来我们从源码的角度了解RecordWriter具体处理数据的逻辑。在RecordWriterOutput中调用pushToRecordWriter方法将数据写出。

在这里插入图片描述

通过recordWriter.emit(serializationDelegate)方法,将数据元素发送到RecordWriter中进行处理。主要逻辑如下

  1. 序列化数据为ByteBuffer二进制数据,并缓存在SpanningRecordSerializer.serializationBuffer对象中。
  2. 将序列化器生成的中间数据复制到指定分区中,实际上就是将ByteBuffer数据复制到BufferBuiler对象中。
  3. 如果BufferBuiler中存储了完整的数据元素,就会清空序列化器的中间数据,因为序列化器中累积的数据不宜过大。
protected void emit(T record, int targetSubpartition) throws IOException {  checkErroneous();  targetPartition.emitRecord(serializeRecord(serializer, record), targetSubpartition);  if (flushAlways) {  targetPartition.flush(targetSubpartition);  }  
}protected void emit(T record, int targetChannel) throws IOException, InterruptedException {checkErroneous();// 数据序列化serializer.serializeRecord(record);// 将序列化器中的数据复制到指定分区中if (copyFromSerializerToTargetChannel(targetChannel)) {// 清空序列化器serializer.prune();}
}

 

2.1. Serializer的实现逻辑

接着了解如何将序列化器中的数据转换成Buffer并存储到ResultPartiton中,最终将数据发送到下游。

a. SpanningRecordSerializer的实现

SpanningRecordSerializer实现将序列化后的BytesBuffer数据写入BufferBuilder。

SpanningRecordSerializer对象主要包含了DataOutputSerializer serializationBuffer和ByteBuffer dataBuffer两个成员变量。

  • DataOutputSerializer可以将数据转换成二进制格式并存储在byte[]数组中。在serialization中会调用serializationBuffer.wrapAsByteBuffer()方法,将serializationBuffer中生成的byte[]数组转换成ByteBuffer数据结构,并赋值给dataBuffer对象。
  • ByteBuffer是Java NIO中用于对二进制数据进行操作的Buffer接口,底层有DirectByteBuffer和HeapByteBuffer等实现,通过ByteBuffer提供的方法,可以轻松实现对二进制数据的操作。

 

b. SpanningRecordSerializer中如何对数据元素进行序列化

SpanningRecordSerializer.serializeRecord()方法主要逻辑如下。

1)清理serializationBuffer的中间数据,实际上就是将byte[]数组的position参数置为0。
2)设定serialization buffer的初始容量,默认不小于4。
3)将数据元素写入serializationBuffer的bytes[]数组。(所有数据元素都实现了IOReadableWritable接口,可以直接将数据对象转换为二进制格式)
4)获取serializationBuffer的长度信息,并写入serializationBuffer。
5)将serializationBuffer中的byte[]数据封装为java.io.ByteBuffer数据结构,最终赋值到dataBuffer的中间结果中。

public void serializeRecord(T record) throws IOException {if (CHECKED) {if (dataBuffer.hasRemaining()) {throw new IllegalStateException("Pending serialization of previous record.");}}// 首先清理serializationBuffer中的数据serializationBuffer.clear();// 设定serialization buffer数量serializationBuffer.skipBytesToWrite(4);// 将record数据写入serializationBufferrecord.write(serializationBuffer);// 获取serializationBuffer的长度信息并记录到serializationBuffer对象中int len = serializationBuffer.length() - 4;serializationBuffer.setPosition(0);serializationBuffer.writeInt(len);serializationBuffer.skipBytesToWrite(len);// 对serializationBuffer进行wrapp处理,转换成ByteBuffer数据结构dataBuffer = serializationBuffer.wrapAsByteBuffer();
}

Flink 1.12版本中RecordWriter就提供了serializeRecord的能力,没有单拎出来实现。

 

2.2. 将ByteBuffer中间数据写入BufferBuilder

首先BufferBuilder用于构建完整的Buffer数据。在copyFromSerializerToTargetChannel()方法中实现了将RecordSerializer中的ByteBuffer中间数据写入BufferBuilder的逻辑:

  1. 对序列化器进行Reset操作,重置初始化位置。
  2. 将序列化器的ByteBuffer中间数据写入BufferBuilder。
  3. 判断当前BufferBuilder是否构建了完整的Buffer数据,完成BufferBuilder中Buffer的构建。
  4. 判断SerializationResult中是否具有完整的数据元素,如果是则将pruneTriggered置为True,然后清空当前的BufferBuilder,并跳出循环。
  5. 创建新的bufferBuilder,继续从序列化器中将中间数据复制到BufferBuilder中。
  6. 指定flushAlways参数为True,调用flushTargetPartition()方法将数据写入ResultPartition。为防止过度频繁地将数据写入ResultPartiton,在RecordWriter中会有独立的outputFlusher线程(在构造器中),周期性地将构建出来的Buffer数据推送到ResultPartiton本地队列中存储,默认延迟为100ms。
protected boolean copyFromSerializerToTargetChannel(int targetChannel) throws IOException, InterruptedException {// 对序列化器进行Reset操作,初始化initial positionserializer.reset();// 创建BufferBuilderboolean pruneTriggered = false;BufferBuilder bufferBuilder = getBufferBuilder(targetChannel);// 调用序列化器将数据写入bufferBuilderSerializationResult result = serializer.copyToBufferBuilder(bufferBuilder);// 如果SerializationResult是完整Bufferwhile (result.isFullBuffer()) {// 则完成创建Buffer数据的操作finishBufferBuilder(bufferBuilder);// 如果是完整记录,则将pruneTriggered置为Trueif (result.isFullRecord()) {pruneTriggered = true;emptyCurrentBufferBuilder(targetChannel);break;}// 创建新的bufferBuilder,继续复制序列化器中的数据到BufferBuilder中bufferBuilder = requestNewBufferBuilder(targetChannel);result = serializer.copyToBufferBuilder(bufferBuilder);}checkState(!serializer.hasSerializedData(), "All data should be written at once");// 如果指定的flushAlways,则直接调用flushTargetPartition将数据写入ResultPartitionif (flushAlways) {flushTargetPartition(targetChannel);}return pruneTriggered;
}

 

二. BufferBuilder申请资源并创建

1. ChannelSelectorRecordWriter创建BufferBuilder

在ChannelSelectorRecordWriter.getBufferBuilder()方法中定义了BufferBuilder的创建过程。

//1. targetChannel确认数据写入的分区,ID与下游InputGate中的InputChannelID是对应的
//2. 
public BufferBuilder getBufferBuilder(int targetChannel) throws IOException, InterruptedException {//在ChannelSelectorRecordWriter中维护了//bufferBuilders[]数组,用于存储创建好的BufferBuilder对象if (bufferBuilders[targetChannel] != null) {return bufferBuilders[targetChannel];} else {//只有在无法从bufferBuilders[]中获取BufferBuilder时,//才会调用requestNewBufferBuilder()方法创建新的BufferBuilder对象。return requestNewBufferBuilder(targetChannel);}
}

requestNewBufferBuilder()方法逻辑如下

  1. 检查bufferBuilders[]的状态,确保bufferBuilders[targetChannel]为空或者bufferBuilders[targetChannel].isFinished()方法返回值为True。
  2. 调用targetPartition.getBufferBuilder()方法获取新的BufferBuilder,这里的targetPartition就是前面提到的ResultPartition。在ResultPartition中会向LocalBufferPool申请Buffer内存空间,用于存储序列化后的ByteBuffer数据。
  3. 向targetPartition添加通过bufferBuilder构建的BufferConsumer对象,bufferBuilder和BufferConsumer内部维护了同一个Buffer数据。BufferConsumer会被存储到ResultSubpartition的BufferConsumer队列中。
  4. 将创建好的bufferBuilder添加至数组,用于下次直接获取和构建BufferConsumer对象。
public BufferBuilder requestNewBufferBuilder(int targetChannel) throws IOException, InterruptedException {checkState(bufferBuilders[targetChannel] == null || bufferBuilders[targetChannel].isFinished());// 调用targetPartition获取BufferBuilderBufferBuilder bufferBuilder = targetPartition.getBufferBuilder();// 向targetPartition中添加BufferConsumertargetPartition.addBufferConsumer(bufferBuilder.createBufferConsumer(),targetChannel);// 将创建好的bufferBuilder添加至数组bufferBuilders[targetChannel] = bufferBuilder;return bufferBuilder;
}

 

2. BroadcastRecordWriter创建BufferBuilder

在BroadcastRecordWriter内部创建BufferBuilder的过程中,会将创建的bufferConsumer对象添加到所有的ResultSubPartition中,实现将Buffer数据下发至所有InputChannel,如下代码:

public BufferBuilder requestNewBufferBuilder(int targetChannel) throws IOException, InterruptedException {checkState(bufferBuilder == null || bufferBuilder.isFinished());BufferBuilder builder = targetPartition.getBufferBuilder();if (randomTriggered) {targetPartition.addBufferConsumer(builder.createBufferConsumer(), targetChannel);} else {try (BufferConsumer bufferConsumer = builder.createBufferConsumer()) {for (int channel = 0; channel < numberOfChannels; channel++) {targetPartition.addBufferConsumer(bufferConsumer.copy(), channel);}}}bufferBuilder = builder;return builder;
}

 

以上步骤就是在RecordWriter组件中将数据元素序列化成二进制格式,然后通过BufferBuilder构建成Buffer类型数据,最终存储在ResultPartition的ResultSubPartition中。

这是从Task的层面了解数据网络传输过程,下篇了解在TaskManager中如何构建底层的网络传输通道。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/725179.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 中 string常用方法及相关的例子

我将为您详细讲解 Java 中 String 类的常用方法及其相关例子。String 类是 Java 中最常用的类之一&#xff0c;它代表字符串&#xff0c;提供了许多用于操作字符串的方法。 1. 字符串比较 - equals(Object obj): 比较字符串的内容是否相等。 - equalsIgnoreCase(String str): 比…

code: 500 ] This subject is anonymous - it does not have any identifying

项目场景&#xff1a; 相关背景&#xff1a; 使用idea 开发java 项目&#xff0c;前端页面请求 页面中相关的接口时&#xff0c;idea 控制台有报错信息出现&#xff0c;前端请求失败。 问题描述 问题&#xff1a; 使用idea 开发java 项目&#xff0c;前端页面请求 页面中相…

【three.js】22. Imported Models导入模型

22. Imported Models导入模型 介绍 Three.js 可以让你创建很多原始几何体&#xff0c;但是当涉及到更复杂的形状时&#xff0c;我们最好使用专用的 3D 软件建模。 在本课中&#xff0c;我们将使用已经制作好的模型&#xff0c;但我们将在以后的课程中学习如何完全在 3D 软件中…

【详识C语言】自定义类型之二:枚举

本章重点 枚举 枚举类型的定义 枚举的优点 枚举的使用 枚举 枚举顾名思义就是一一列举。 把可能的取值一一列举。 比如我们现实生活中&#xff1a; 一周的星期一到星期日是有限的7天&#xff0c;可以一一列举。 性别有&#xff1a;男、女、保密&#xff0c;也可以一一列举。…

vscode c/c++ 检测到 #include 错误。请更新 includePath。

问题背景 使用vscode打开项目后&#xff0c;头文件显示红色波浪线&#xff0c;没有引入。 检测到 #include 错误。请更新 includePath。已为此翻译单元(xxx)禁用波形曲线。 解决方法 gcc -v -E -x c - 显示所有头文件路径。 打开c_cpp_properties.json文件&#xff0c;粘贴…

【C++】类与对象(上篇)

一.类的引入 C与C语言比较起来&#xff0c;C引入了一个新的概念&#xff0c;叫做类。那么在C中&#xff0c;类又是什么呢&#xff1f; 在C中&#xff0c;类与C语言中的结构体相似&#xff0c;但不同的是&#xff0c;C中的类中&#xff0c;不仅可以定义变量&#xff0c;还能定义…

【pve】创建虚拟机

创建虚拟机 1.创建虚拟机-常规2.操作系统3. 系统4.磁盘5. cpu6.内存7.网络8.确认9.安装完成 1.创建虚拟机-常规 2.操作系统 这里选择自己的操作系统和系统的类别。 3. 系统 EFI存储选择自己磁盘 4.磁盘 调整适当的磁盘空间。 5. cpu 6.内存 根据自己的内存大小进行配…

最近开发中遇到的一些问题

puppeteer下载失败问题 使用的淘宝镜像&#xff0c;但执行命令npm i puppeteer之后&#xff0c;报错&#xff1a; npm ERR! code 1 npm ERR! path E:\项目-临时\test_install_puppeteer\node_modules\puppeteer npm ERR! command failed npm ERR! command C:\WINDOWS\system3…

cuda WSL2 无需单独安装

https://docs.nvidia.com/cuda/wsl-user-guide/index.html 这个写的很详细

TypeScript 哲学 - everyday Type

1、 2、TypeScript a structurally typed type system. 3、 type vs interface 3、literal reference 4、non-null assertion operator

微信小程序云开发教程——墨刀原型工具入门(Axure导入)

引言 作为一个小白&#xff0c;小北要怎么在短时间内快速学会微信小程序原型设计&#xff1f; “时间紧&#xff0c;任务重”&#xff0c;这意味着学习时必须把握微信小程序原型设计中的重点、难点&#xff0c;而非面面俱到。 要在短时间内理解、掌握一个工具的使用&#xf…

快速上手:剧本杀dm预约平台小程序的制作流程

在当今的娱乐市场中&#xff0c;剧本杀已经成为一种备受欢迎的娱乐方式。为了给玩家提供更好的服务和体验&#xff0c;开发一个剧本杀DM预约平台小程序是至关重要的。下面&#xff0c;我们将详细介绍如何使用乔拓云第三方平台开发这样一个预约平台。 首先&#xff0c;打开乔拓云…

软件测试需求分析如何编写?为什么要进行测试需求分析?

在软件开发的过程中&#xff0c;软件测试需求分析是至关重要的一个环节。测试需求分析是指对待测软件的需求进行全面细致的分析&#xff0c;明确软件测试的目标和范围&#xff0c;为测试活动的进行提供指导。通过对软件需求的详细分析&#xff0c;可以确保测试人员清楚了解软件…

动手学深度学习-现代循环神经网络(GRU、LSTM、编码器-解码器等)

现代循环神经网络 上一章节&#xff08;循环神经网络&#xff09;介绍了循环神经网络的基础知识&#xff0c;这种网络可以更好的处理序列数据。我们在文本数据上实现了基于循环神经网络的语言模型&#xff0c;但是对于当今各种各样的序列学习问题&#xff0c;这些技术可能不够…

网络学习:Vlan间路由

目录 一、vlan间路由实现的方法 二、精确匹配转发&#xff08;交换机&#xff09;流程 三、最长匹配转发&#xff08;路由器&#xff09; 四、交换机最长匹配转发 五、总结 一、vlan间路由实现的方法 方法1&#xff1a;使用路由器的物理接口 特点&#xff1a;在路由器上…

spring 事务失效的 12 种场景

文章目录 spring 事务失效的 12 种场景一、事务不生效1.访问权限问题2. 方法用 final 修饰3.方法内部调用&#xff08;自己玩自己&#xff09;3.1 新加一个 Service 方法3.2 在该 Service 类中注入自己3.3 通过 AopContent 类 4.Bean没有纳入Spring IOC容器管理5.多线程调用&am…

图像超分辨率:Fast Nearest Convolution for Real-Time Efficient Image Super-Resolution

9.Fast Nearest Convolution for Real-Time Efficient Image Super-Resolution 提出一种适用移动端的超分网络 一些tensor op 的推理时间 一些卷积结构的推理时间 网络结构NCNet 主干网络预测的是 残差&#xff0c;什么的残差&#xff1f; 是最近邻插值图像与 ground-truth的…

‘utf-8‘ codec can‘t decode byte 0xc1 in position 0: invalid start byte

‘utf-8’ codec can’t decode byte 0xc1 in position 0: invalid start byte 1、使用python3.6版运行django程序时报错 如图&#xff1a; 2、原因 这是由于计算机使用了中文命名。 3、解决方案 1、把计算机修改为英文名 2、在socket.py的第673行修改 name‘hello’

09-Linux部署Redis

Linux部署Redis 简介 Redis&#xff0c;全称为Remote Dictionary Server&#xff08;远程字典服务&#xff09;&#xff0c;是一个开源的、使用ANSI C语言编写的、支持网络连接的、基于内存的、同时支持持久化的日志型Key-Value数据库&#xff0c;并提供多种语言的API。 Red…

状压dp详解,棋盘式、集合型,OJ详解

文章目录 零、引例-小国王1.问题描述2.暴力枚举3.多维dp4.维度压缩 一、状压dp1.认识状压dp2.棋盘式(基于连通性)2.1小国王2.1.1题目链接2.1.2思路分析2.1.3AC代码 2.2玉米田2.2.1题目链接2.2.2思路分析2.2.3AC代码 2.3炮兵阵地2.3.1题目链接2.3.2思路分析2.3.3AC代码 2.4蒙德里…