Apache Arrow IPC 消息格式

news2024/11/14 3:06:52

在这里插入图片描述
Apache Arrow 的 IPC(Inter-Process Communication,进程间通信)消息格式是一种用于在不同进程间高效传输数据的序列化格式,它允许不同系统或语言环境中的应用程序以统一的方式交换数据,而无需关心数据的具体存储细节。其 IPC 消息格式包括两种主要的二进制格式: Streaming 流式格式和 RandomAccessFile 随机访问格式 ,本文介绍了这两种消息格式的具体形式和其元数据使用到的 FlatBuffers 序列化方法,并通过一个简例说明消息格式的实际使用

01 数据流格式


在 Apache Arrow 的进程间通信(Inter-Process Communication)中提供了 Streaming 和 RandomAccessFile 两种二进制格式,用于读取和写入 RecordBatch 数据

  • Streaming Format:这种格式用于发送任意长度的 RecordBatch 序列,但它必须从开始到结束顺序处理,不支持随机访问,所以 Streaming Format 适用于数据流或网络传输等应用场景
  • RandomAccessFile Format:这种格式用于序列化固定数量的 RecordBatch,由于该格式包含有关 RecordBatch 数量和位置的元数据,所以支持随机访问,因此在与内存映射一起使用时非常有用

在之前介绍 RecordBatch 时(article url),简单介绍了 Arrow IPC Stream 通常是按照 Schema 后面跟一个或多个 RecordBatch 的方式组织;如下图所示 Stream Format 中使用三种消息类型来传输数据:Schema, RecordBatch, DictionaryBatch,如果 Schema 中包含使用字段编码(dictionary-encoded)的数组,那么 Stream 中还会有 DictionaryBatch 格式的消息

Arrow IPC 传输数据时封装的消息格式 Stream Format 如下图所示:

  • 消息开头是 8 个字节 0xFFFFFFFF表示有效消息的开始;
  • 接着是一个表示 Schema 元数据信息长度的 32-bit 整数,该整数使用小端字节序编码(Little Endian Int);
  • 然后是实际的 Schema 元数据;
  • 接着是为了确保数据结构的对齐实现跨平台特性,可能包含填充字节使得整个消息的总长度是 8 字节的倍数;
  • 最后就是包含要传输数据 RecordBatchs 的消息体

RandomAccessFile Format 的文件格式只是 Streaming Format 的一个扩展,如下图所示仅比 Streaming Format 多了表示文件开头结尾的 Magic String 和包含元数据信息的Footer,具体来说包括:

  • Magic String (**ARROW1**):表示文件开头,其中 ARROW1是一个固定的字节序列在源码cpp\src\arrow\ipc\metadata_internal.h中被定义为kArrowMagicBytes,用于快速检查文件是否是 Arrow 格式
  • Empty padding to 8-byte boundary:为了确保文件结构的对齐,可能包含空的填充字节,使得从Magic String开始到实际数据部分的开始是 8 字节边界对齐的
  • Data Using the Streaming Format with EOS indicator:文件的主体,包含了使用 Streaming Format序列化的数据;数据最后面可能跟着一个 End Of Stream(EOS)指示器,表示没有更多的数据
  • FlatBuffer Footer Message Bytes:这部分是 FlatBuffer 形式的 Footer 消息,包含了Streaming Format中 Schema 的拷贝和文件中每个数据块在内存中的偏移量(Offset)和长度,这些元数据信息是 RandomAccessFile Format 文件格式实现随机访问的关键
  • Footer Size:一个 32 位的整数采用小端字节序编码,表示 Footer 消息的字节长度
  • Magic String (**ARROW1**):文件结尾标识,与文件开始处相同,用于标识文件的结束

02 元数据序列化


Arrow IPC Format 中可以看到消息格式中的元数据信息都是使用 FlatBuffers 来进行序列化的,为什么这么做呢?

对于进行间通信而言,消息在不同进程之间传递需要进行序列化和反序列化 serialization/deserialization,数据序列化和反序列化的速度越快,数据发送的速度就越快;所以,对于 IPC 过程来说,序列化和反序列化的开销十分重要

为了优化序列化和反序列化的开销,Apache Arrow IPC Format 采用了高性能序列化方式 FlatBuffers 来对元数据 metadata 进行序列化,对于消息体直接使用数据缓冲区 raw data buffers 传输数据而不需要进行序列化操作

在介绍物理布局时(Apache Arrow 的列式内存格式)已经详细介绍过了 raw data buffers 的传输格式,下面详细介绍一下元数据的序列化方式 FlatBuffers

FlatBuffers 相关内容重度参考:深入浅出FlatBuffers原理-阿里云开发者社区 (aliyun.com)

2.1 FlatBuffers 数据结构定义

首先简单介绍一下 FlatBuffers,它是一个由 Google 开发的跨平台序列化库,允许直接访问序列化的数据结构,无需解析成中间格式;FlatBuffers 支持零拷贝反序列化,即在反序列化过程中无需复制数据到内存的其他部分,从而避免了额外的内存分配和临时对象的创建;基于诸多高性能特性,访问 FlatBuffers 序列化的数据比访问 JSON、CSV、Protocol Buffers 等需要解析和复制步骤的格式要高效得多

FlatBuffers 通过 Schema 定义语言来描述要序列化的数据结构,其语法和结构与 C 语言类似是一种类型安全的 IDL(接口描述语言)。下面结合 Apache Arrow 中定义 Schema 的 format\Schema.fbs实例来说明其基本的语法:

  1. namespace:定义命名空间,用于组织和区分不同的数据结构
namespace org.apache.arrow.flatbuf;
  1. enum:定义枚举类型,用于限定变量的取值范围
enum Endianness:short { Little, Big }
  1. struct:定义结构体,类似于 C 语言中的struct,用于创建复杂的数据类型
struct Buffer {
  offset: long;
  length: long;
}
  1. table:定义表,类似于数据库中的表,可以包含多个字段,并且字段的值可以是不同的类型
table Schema {
  endianness: Endianness=Little;

  fields: [Field];
  // User-defined metadata
  custom_metadata: [ KeyValue ];

  /// Features used in the stream/file.
  features : [ Feature ];
}
  1. root_type:指定序列化文件的根类型,即文件中最顶层的数据类型
root_type Schema;
  1. file_identifier:为文件定义一个唯一的标识符,用于文件格式的验证。
file_identifier:"SCHEMA";
  1. attribute:定义属性,可以用于表或结构体,提供额外的元数据
table Person {
  id:int;
  [deprecated] name:string; // 表示 name 字段已经废弃
}
  1. array:定义固定长度的数组
array Vec3[3] = [ v1, v2, v3 ];
  1. vector:定义动态数组,用于存储一系列相同类型的元素
vector inventory:[ubyte];
  1. union:定义联合体,类似于 C 语言的联合体,可以存储多种不同但兼容的类型
union Type {
  Null,
  Int,
  FloatingPoint,
  Binary,
  Utf8,
  Bool,
  Decimal,
  Date,
  Time,
  Timestamp,
  Interval,
  List,
  Struct_,
  Union,
  FixedSizeBinary,
  FixedSizeList,
  Map,
  Duration,
  LargeBinary,
  LargeUtf8,
  LargeList,
  RunEndEncoded,
  BinaryView,
  Utf8View,
  ListView,
  LargeListView,
}
  1. string:定义字符串类型
table KeyValue {
  key: string;
  value: string;
}

通过这些关键字,可以定义复杂的数据结构,并使用 FlatBuffers 工具生成相应的序列化和反序列化代码;一个使用上述关键字定义的 Arrow 中 Schema 的部分数据结构示例如下:

// 定义命名空间
namespace org.apache.arrow.flatbuf;

// 定义枚举类型
enum Endianness:short { Little, Big }

// 定义结构体
struct Buffer {
  /// The relative offset into the shared memory page where the bytes for this
  /// buffer starts
  offset: long;

  /// The absolute length (in bytes) of the memory buffer. The memory is found
  /// from offset (inclusive) to offset + length (non-inclusive). When building
  /// messages using the encapsulated IPC message, padding bytes may be written
  /// after a buffer, but such padding bytes do not need to be accounted for in
  /// the size here.
  length: long;
}

// 定义表,包含字节序类型、字段、额外元数据等
table Schema {

  /// endianness of the buffer
  /// it is Little Endian by default
  /// if endianness doesn't match the underlying system then the vectors need to be converted
  endianness: Endianness=Little;

  fields: [Field];
  // User-defined metadata
  custom_metadata: [ KeyValue ];

  /// Features used in the stream/file.
  features : [ Feature ];
}

// 指定文件的根类型
root_type Schema;

2.2 FlatBuffers 序列化

FlatBuffers 序列化的主要逻辑是将对象数据存储于一个连续的一维字节数组 ByteBuffer 中;在该数组中,每个对象被划分为两个关键部分,元数据部分:包含必要的索引信息;真实数据部分:存储具体的数据值

与大多数内存中的数据结构不同的是,FlatBuffers 遵循严格的内存对齐规则和字节顺序序规范,确保了序列化数据的跨平台兼容性。此外,对于 table 类型的对象,FlatBuffers 还提供了前向和后向兼容性,以及对 optional字段的支持,从而适应数据格式的演变

除了解析效率以外,二进制格式还带来了另一个优势,数据的二进制表示通常更具有效率;可以使用 4 字节的 uint 而不是 10 个字符来存储 10 位数字的整数

FlatBuffers 序列化基本使用原则:

  • 小端模式:FlatBuffers 对各种基本数据的存储都是按照小端模式来进行的,因为这种模式目前和大部分处理器的存储模式是一致的,可以加快数据读写的数据。
  • 写入数据方向和读取数据方向不同

(图片来源:https://ucc.alicdn.com/pic/developer-ecology/dfd3cda3a651416a8b39780d2528a29b.png)

FlatBuffers 向 ByteBuffer 中写入数据的顺序是从 ByteBuffer 的尾部向头部填充,由于这种增长方向和 ByteBuffer 默认的增长方向不同,因此 FlatBuffers 在向 ByteBuffer 中写入数据的时候就不能依赖 ByteBuffer 的 position 来标记有效数据位置,而是自己维护了一个 space 变量来指明有效数据的位置,在分析 FlatBuffersBuilder 的时候要特别注意这个变量的增长特点

但是,和数据的写入方向不同的是,FlatBuffers 从 ByteBuffer 中解析数据的时候又是按照 ByteBuffer 正常的顺序来进行的。FlatBuffers 这样组织数据存储的好处是,在从左到右解析数据的时候,能够保证最先读取到的就是整个 ByteBuffer 的概要信息(例如 Table 类型的 vtable 字段),方便解析。

2.3 FlatBuffers 反序列化

基于序列化阶段的严格处理,FlatBuffers 的反序列化过程就很简单了;由于在序列化阶段每个字段的偏移量 offset 已被精确记录,因此反序列化本质上是从 ByteBuffer 中的指定偏移量处提取数据

反序列化过程从 root table 开始,沿着二进制流逐步向后读取:

首先,从包含字段偏移量信息的 vtable 中检索所需的偏移量 offset

然后,根据对应的 offset 在相应的 object 中定位到具体的字段,如果字段是引用类型(string、vector、table 等),则读取出 offset 并解析这些引用的 offset,进一步检索它们指向的实际偏移量;对于非引用类型,则直接根据 vtable 中的 offset 定位到数据直接读取

对于标量字段需要区分默认值和非默认值两种情况:对于默认值字段,反序列化时会直接采用由 flatc编译器后生成的文件中记录的默认值读取出来;而对于非默认值字段,二进制流中就会记录该字段的 offset,值也会存储在二进制流中,反序列化时直接根据 offset 读取字段值即可

整个反序列化过程是零拷贝 zero-copy 的,这意味着它不会消耗额外的内存资源,也不会创建数据的副本;并且 FlatBuffers 可以读取任意字段,而不是像 Json 和 protocol buffer 需要读取整个对象以后才能获取某个字段。FlatBuffers 的主要优势就在反序列化这里,所以 FlatBuffers 可以做到解码速度极快,或者说无需解码直接读取。

03 Arrow IPC Stream 简例


下面通过一个简单的 IPC 实例来加深对 Arrow 中二进制数据流传输的理解,该例子没有真正意义上的多进程之间通信,而是简单模拟了向 Arrow IPC Stream 中写入数据,然后从中读取数据并打印,具体过程包括:

  • 根据 JSON 字符串创建一个 RecordBatch

  • 将数据写入到 Arrow IPC Stream 中

  • 从 Arrow IPC Stream 读取该数据并打印

1. 引用必要的库文件

引用与 Arrow IPC Stream 操作相关的 arrow 头文件,以及 json 数据处理等库文件

#include <arrow/api.h>
#include <arrow/ipc/writer.h>
#include <arrow/json/reader.h>
#include <arrow/memory_pool.h>
#include <arrow/result.h>
#include <arrow/status.h>
#include <iostream>
#include <string>

2. 根据 JSON 字符串创建一个 RecordBatch

在介绍 RecordBatch 时提到其由元数据 Schema 和具体数据 Arrays 组成,其中 Schema 又由表示 Arrays 名称和类型的 Fields 构成,下面创建 RecordBatch 也按这样的组织方式进行构建

  • 使用arrow::field函数创建一个字段,接受字段名和字段类型作为参数,并将将创建的字段添加到字段类型列表 fields
  • 使用字段列表 fields 创建一个 Arrow 模式(Schema)对象 schema
  • 使用 Arrow JSON 读取器解析 JSON 数据,并根据 JSON 数据创建一个 Arrow 数组 array
  • 准备好了构建 RecordBatch 的所有组成部分,使用schemaarray创建一个 RecordBatch
    std::shared_ptr<arrow::MemoryPool> mem = arrow::default_memory_pool();
    arrow::DataTypeVector fields;
    auto field = arrow::field("list", arrow::list(arrow::binary()));
    fields.push_back(field);
    auto schema = std::make_shared<arrow::Schema>(fields);

    // Create a JSON reader from a string of data
    auto input_data = R"([
        ["index1"], 
        ["index3", "tag_int"], ["index5", "tag_int"],
        ["index6", "tag_int"], ["index7", "tag_int"], 
        ["index7", "tag_int"],
        ["index8"]
    ])";
    auto reader = arrow::json::Reader::Make(arrow::default_memory_pool(), schema, input_data);

    // Read the data into an Array
    std::shared_ptr<arrow::Array> array;
    ARROW_ASSIGN_OR_RAISE(array, reader->Read());

    // Create a RecordBatch from the Array
    auto record = arrow::RecordBatch::Make(schema, array->length(), {array});

3. 将数据写入到 Arrow IPC Stream 中

使用 arrow::ipc::MakeStreamWriter 创建的 IPC 流写入器,将使用 Slice 从 RecordBatch 提取出的部分数据序列化并写入到流中

    // Create a slice of the RecordBatch
    auto slice = record->Slice(1, 2);

    // Write the sliced RecordBatch to an IPC stream
    ARROW_ASSIGN_OR_RAISE(auto output, arrow::ipc::MakeStreamWriter(arrow::default_memory_pool(), &slice->schema()));
    output->WriteRecordBatch(*slice);

4. 从 Arrow IPC Stream 读取该数据并打印

创建一个的 arrow::ipc::RecordBatchStreamReader对象 reader2,该读取器从 output 对象的序列化输出中创建的,output->Finish()方法返回一个包含序列化数据的 Buffer;然后从 reader2 中读取数据并打印输出

    // Read the IPC stream back into a RecordBatch
    std::shared_ptr<arrow::RecordBatchReader> reader2;
    ARROW_ASSIGN_OR_RAISE(reader2, arrow::ipc::RecordBatchStreamReader::Open(std::make_shared<arrow::io::BufferReader>(output->Finish())));

    // Print the contents of the RecordBatch
    std::shared_ptr<arrow::RecordBatch> readbatch;
    ARROW_ASSIGN_OR_RAISE(readbatch, reader2->ReadRecordBatch());
    std::cout << readbatch->ToString() << std::endl;

上述实例的完整代码实现如下:

#include <arrow/api.h>
#include <arrow/ipc/writer.h>
#include <arrow/json/reader.h>
#include <arrow/memory_pool.h>
#include <arrow/result.h>
#include <arrow/status.h>
#include <iostream>
#include <string>

int main() {
    std::shared_ptr<arrow::MemoryPool> mem = arrow::default_memory_pool();
    arrow::DataTypeVector fields;
    auto field = arrow::field("list", arrow::list(arrow::binary()));
    fields.push_back(field);
    auto schema = std::make_shared<arrow::Schema>(fields);

    // Create a JSON reader from a string of data
    auto input_data = R"([
        ["index1"], 
        ["index3", "tag_int"], ["index5", "tag_int"],
        ["index6", "tag_int"], ["index7", "tag_int"], 
        ["index7", "tag_int"],
        ["index8"]
    ])";
    auto reader = arrow::json::Reader::Make(arrow::default_memory_pool(), schema, input_data);

    // Read the data into an Array
    std::shared_ptr<arrow::Array> array;
    ARROW_ASSIGN_OR_RAISE(array, reader->Read());

    // Create a RecordBatch from the Array
    auto record = arrow::RecordBatch::Make(schema, array->length(), {array});

    // Create a slice of the RecordBatch
    auto slice = record->Slice(1, 2);

    // Write the sliced RecordBatch to an IPC stream
    ARROW_ASSIGN_OR_RAISE(auto output, arrow::ipc::MakeStreamWriter(arrow::default_memory_pool(), &slice->schema()));
    output->WriteRecordBatch(*slice);

    // Read the IPC stream back into a RecordBatch
    std::shared_ptr<arrow::RecordBatchReader> reader2;
    ARROW_ASSIGN_OR_RAISE(reader2, arrow::ipc::RecordBatchStreamReader::Open(std::make_shared<arrow::io::BufferReader>(output->Finish())));

    // Print the contents of the RecordBatch
    std::cout << slice->ToString() << std::endl;

    return 0;
}

如果文章对你有帮助,欢迎 点赞收藏 👍 ⭐️ 💬,如果还能够 点击关注,那真的是对我最大的鼓励 🔥 🔥 🔥

参考资料

Wes McKinney - Streaming Columnar Data with Apache Arrow

深入浅出FlatBuffers原理-阿里云开发者社区 (aliyun.com)

Improving Facebook’s performance on Android with FlatBuffers - Engineering at Meta (fb.com)

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

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

相关文章

【网络安全】身份认证+wan优化+终端控制

用户身份认证 在允许用户访问你的网络时对其进行验证是至关重要的。不幸的是很多情况下&#xff0c;简单的用户名与密码验证并不可靠。公司通常需要更强大的针对访问信息价值较高系统(例如网络管理员系统与财务系统)的用户群体的验证。 双因子身份验证是根据“你知道的”和“你…

faiss安装 (CPU版本)

faiss版本 faiss-v1.7.4 cd faiss-v1.7.4cmake -B build . -DBUILD_TESTINGOFF -DFAISS_ENABLE_GPUOFF -DFAISS_ENABLE_PYTHONOFFmake -C build -j faiss&#xff1b; 默认安装路径如下 -- Installing: /usr/local/lib64/libfaiss.a -- Installing: /usr/local/include/faiss…

VMware vCenter Server 7.0U3s 发布下载,新增功能概览

VMware vCenter Server 7.0U3s 下载 - 集中管理 vSphere 环境 Server Management Software | vCenter | 集中管理 vSphere 环境 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vcenter-7-u3/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者…

WebGL颜色与纹理

WEBGL中的着色器变量包括以下种类&#xff1a; 属性变量&#xff08;Attribute Variables&#xff09;&#xff1a;这些变量用于接收从应用程序中传递的顶点数据&#xff0c;比如顶点位置和颜色&#xff0c;是只读的不可修改。统一变量&#xff08;Uniform Variables&#xff…

通过docker启动ElasticSearch后为ElasticSearch设置用户和密码

文章目录 0. 前言1. 没有设置用户名和密码的情况2. 为ElasticSearch设置用户名和密码2.1 进入 ElasticSearch 容器内部2.2 修改 ElasticSearch 的配置文件2.3 设置用户名和密码 3. 在 kibana 容器中指定访问 ElasticSearch 的用户名和密码4. 设置用户名和密码后的情况4.1 访问 …

QT For Android开发-打开PPT文件

一、前言 需求&#xff1a; Qt开发Android程序过程中&#xff0c;点击按钮就打开一个PPT文件。 Qt在Windows上要打开PPT文件或者其他文件很容易。可以使用QDesktopServices打开文件&#xff0c;非常方便。QDesktopServices提供了静态接口调用系统级别的功能。 这里用的QDesk…

C语言理解 —— printf 格式化输出

目 录 printf 函数一、短整型输出二、长整型输出三、浮点型输出四、字符型输出五、字符串输出六、注意问题 printf 函数 在软件开发过程中&#xff0c;通常需要打印一些字符串信息&#xff0c;或把一些变量值输出到上位机显示。打印函数printf是最常用的。 一般格式&#xff…

架构设计笔记-5-软件工程基础知识

知识要点 按软件过程活动&#xff0c;将软件工具分为软件开发工具、软件维护工具、软件管理和软件支持工具。 软件开发工具&#xff1a;需求分析工具、设计工具、编码与排错工具。 软件维护工具&#xff1a;版本控制工具、文档分析工具、开发信息库工具、逆向工程工具、再工…

计算机毕业设计 基于Python的荣誉证书管理系统 Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

【永磁同步电机(PMSM)】 6. 矢量空间算法(SVPWM)

【永磁同步电机&#xff08;PMSM&#xff09;】 6. 矢量空间算法&#xff08;SVPWM&#xff09; 1. SVPWM 的基本原理1.1 SVPWM 的优点1.2 SVPWM 的电路拓扑1.3 连续旋转的空间矢量 2. SVPWM 的算法实现2.1 电压矢量组合方案2.2 SVPWM 的实现步骤 3. 基于 Simulink 的 SVPWM 仿…

软考高级:系统安全 -区块链特点:去中心化、开放性、自治性、安全性、匿名性

讲解 生活化例子 想象一下&#xff0c;你和朋友们玩一个共享账本的游戏。每个人都可以在账本上记账&#xff0c;没人可以单独改动账本&#xff0c;大家都可以随时查看账本内容&#xff0c;也不用再信任某个单独的人来管理账本。这就类似于区块链的工作原理。 概念讲解 去中…

软考高级:敏捷开发 SCRUM

生活化例子 想象你和朋友们要组织一场周末聚会。你们首先会决定聚会的主题、场地、时间等大方向&#xff0c;然后每个人分配不同的任务&#xff0c;比如有人负责买零食&#xff0c;有人负责准备音乐&#xff0c;有人负责布置场地。你们每天都简单沟通下进展&#xff0c;看看大…

【C++笔试强训】如何成为算法糕手Day1

学习编程就得循环渐进&#xff0c;扎实基础&#xff0c;勿在浮沙筑高台 循环渐进Forward-CSDN博客 笔试强训第一天 目录 循环渐进Forward-CSDN博客 第一题&#xff1a;两个数组的交集 暴力循环法&#xff1a; 哈希法 &#xff1a; 数组下标法&#xff1a; 第二题&#x…

“滑动窗口”思想在算法里面的应用

目录 一 无重复字符串的最长子串 链接&#xff1a;无重复字符的最长子串 1. 题目分析 解法一&#xff1a;暴力求解 借助2个“指针”&#xff1a;left &#xff0c; right 指针&#xff0c;依次固定left 指针&#xff0c;让right指针进行遍历&#xff0c;每遇到一个最大的 …

24/9/19 算法笔记 kaggle BankChurn数据分类

题目是要预测银行里什么样的客户会流失&#xff0c;流失的概率是多少 我这边先展示一下我写的二分类的算法 import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.linear_model impo…

亚马逊IP关联揭秘:发生ip关联如何处理

在亚马逊这一全球领先的电商平台上&#xff0c;IP关联是一个不可忽视的问题&#xff0c;尤其是对于多账号运营的卖家而言。本文将深入解析亚马逊IP关联的含义、影响以及应对策略&#xff0c;帮助卖家更好地理解和应对这一问题。 什么是亚马逊IP关联&#xff1f; 亚马逊IP关联…

awk从0学习

1. 入门 1.1 什么是 awk&#xff1f; ①Awk是一种文本处理工具&#xff0c;适用于处理结构化数据&#xff0c;例如表格数据。 ②它可以读取一个或多个文本文件&#xff0c;并执行模式扫描和处理等指定的操作。 ③基本逻辑涉及数据的提取&#xff0c;排序和计算。 ④支持复…

Leetcode 2464. 有效分割中的最少子数组数目

1.题目基本信息 1.1.题目描述 给定一个整数数组 nums。 如果要将整数数组 nums 拆分为 子数组 后是 有效的&#xff0c;则必须满足: 每个子数组的第一个和最后一个元素的最大公约数 大于 1&#xff0c;且 nums 的每个元素只属于一个子数组。 返回 nums 的 有效 子数组拆分中…

Lumos学习王佩丰Excel第十五讲:条件格式与公式

一、使用简单的条件格式 1、为特定范围的数值标记特殊颜色 条件格式-需选择设定范围&#xff08;大于/小于/介于/......&#xff09;&#xff1a; 数值会动态根据条件判断更新颜色&#xff1a; 模糊匹配&#xff0b;条件格式&#xff1a;选择包含部分文本的特殊值 2、查找重复…

linux-----进程控制

提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、fork()函数 返回值&#xff1a;子进程返回0&#xff0c;父进程返回子进程的id,出错就返回-1. fork创建子进程&#xff0c;如果父子一方发生写入时&#xff0c;就会发生写实拷贝&#xff0c;操作系统就…