JVM上高性能数据格式库包Apache Arrow入门和架构详解(Gkatziouras)


Posted in Servers onMay 26, 2021

Apache Arrow是是各种大数据工具(包括BigQuery)使用的一种流行格式,它是平面和分层数据的存储格式。它是一种加快应用程序内存密集型。

数据处理和数据科学领域中的常用库: Apache Arrow 。诸如Apache Parquet,Apache Spark,pandas之类的开放源代码项目以及许多商业或封闭源代码服务都使用Arrow。它提供以下功能:

  • 内存计算
  • 标准化的柱状存储格式
  • 一个IPC和RPC框架,分别用于进程和节点之间的数据交换

让我们看一看在Arrow出现之前事物是如何工作的:

JVM上高性能数据格式库包Apache Arrow入门和架构详解(Gkatziouras)

我们可以看到,为了使Spark从Parquet文件中读取数据,我们需要以Parquet格式读取和反序列化数据。这要求我们通过将数据加载到内存中来制作数据的完整副本。首先,我们将数据读入内存缓冲区,然后使用Parquet的转换方法将数据(例如字符串或数字)转换为我们的编程语言的表示形式。这是必需的,因为Parquet表示的数字与Python编程语言表示的数字不同。

由于许多原因,这对于性能来说是一个很大的问题:

  • 我们正在复制数据并在其上运行转换步骤。数据的格式不同,我们需要对所有数据进行读取和转换,然后再对数据进行任何计算。
  • 我们正在加载的数据必须放入内存中。您只有8GB的RAM,数据是10GB吗?你真倒霉!

现在,让我们看一下Apache Arrow如何改进这一点:

JVM上高性能数据格式库包Apache Arrow入门和架构详解(Gkatziouras)

Arrow无需复制和转换数据,而是了解如何直接读取和操作数据。为此,Arrow社区定义了一种新的文件格式以及直接对序列化数据起作用的操作。可以直接从磁盘读取此数据格式,而无需将其加载到内存中并转换/反序列化数据。当然,部分数据仍将被加载到RAM中,但您的数据不必放入内存中。Arrow使用其文件的内存映射功能,仅在必要和可能的情况下将尽可能多的数据加载到内存中。

Apache Arrow支持以下语言:

  • C++
  • C#
  • Go
  • Java
  • JavaScript
  • Rust
  • Python (through the C++ library)
  • Ruby (through the C++ library)
  • R (through the C++ library)
  • MATLAB (through the C++ library).

Arrow特点

Arrow首先是提供用于内存计算的列式数据结构的库,可以将任何数据解压缩并解码为Arrow柱状数据结构,以便随后可以对解码后的数据进行内存内分析。Arrow列格式具有一些不错的属性:随机访问为O(1),每个值单元格在内存中的前一个和后一个相邻,因此进行迭代非常有效。

Apache Arrow定义了一种二进制“序列化”协议,用于安排Arrow列数组的集合(称为“记录批处理”),该数组可用于消息传递和进程间通信。您可以将协议放在任何地方,包括磁盘上,以后可以对其进行内存映射或读入内存并发送到其他地方。

Arrow协议的设计目的是使您可以“映射”一个Arrow数据块而不进行任何反序列化,因此对磁盘上的Arrow协议数据执行分析可以使用内存映射并有效地支付零成本。该协议用于很多事情,例如Spark SQL和Python之间的流数据,用于针对Spark SQL数据块运行pandas函数,这些被称为“ pandas udfs”。

Arrow是为内存而设计的(但是您可以将其放在磁盘上,然后再进行内存映射)。它们旨在相互兼容,并在应用程序中一起使用,而其竞争对手Apache Parquet文件是为磁盘存储而设计的。

优点:Apache Arrow为平面和分层数据定义了一种独立于语言的列式存储格式,该格式组织为在CPU和GPU等现代硬件上进行高效的分析操作而组织。Arrow存储器格式还支持零拷贝读取,以实现闪电般的数据访问,而无需序列化开销。

Java的Apache Arrow

导入库:

<dependency>
    <groupId>org.apache.arrow</groupId>
    <artifactId>arrow-memory-netty</artifactId>
    <version>${arrow.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.arrow</groupId>
    <artifactId>arrow-vector</artifactId>
    <version>${arrow.version}</version>
</dependency>

在开始之前,必须了解对于Arrow的读/写操作,使用了字节缓冲区。诸如读取和写入之类的操作是字节的连续交换。为了提高效率,Arrow附带了一个缓冲区分配器,该缓冲区分配器可以具有一定的大小,也可以具有自动扩展功能。支持分配管理的库是arrow-memory-netty和arrow-memory-unsafe。我们这里使用netty。

用Arrow存储数据需要一个模式,模式可以通过编程定义:

package com.gkatzioura.arrow;

import java.io.IOException;

import java.util.List;

import org.apache.arrow.vector.types.pojo.ArrowType;

import org.apache.arrow.vector.types.pojo.Field;

import org.apache.arrow.vector.types.pojo.FieldType;

import org.apache.arrow.vector.types.pojo.Schema;

public class SchemaFactory {

public static Schema DEFAULT_SCHEMA = createDefault();

public static Schema createDefault() {

var strField = new Field("col1", FieldType.nullable(new ArrowType.Utf8()), null);

var intField = new Field("col2", FieldType.nullable(new ArrowType.Int(32, true)), null);

return new Schema(List.of(strField, intField));

}

public static Schema schemaWithChildren() {

var amount = new Field("amount", FieldType.nullable(new ArrowType.Decimal(19,4,128)), null);

var currency = new Field("currency",FieldType.nullable(new ArrowType.Utf8()), null);

var itemField = new Field("item", FieldType.nullable(new ArrowType.Utf8()), List.of(amount,currency));

return new Schema(List.of(itemField));

}

public static Schema fromJson(String jsonString) {

try {

return Schema.fromJSON(jsonString);

} catch (IOException e) {

throw new ArrowExampleException(e);

}

}

}

他们也有一个可解析的json表示形式:

{
  "fields" : [ {
    "name" : "col1",
    "nullable" : true,
    "type" : {
      "name" : "utf8"
    },
    "children" : [ ]
  }, {
    "name" : "col2",
    "nullable" : true,
    "type" : {
      "name" : "int",
      "bitWidth" : 32,
      "isSigned" : true
    },
    "children" : [ ]
  } ]
}

另外,就像Avro一样,您可以在字段上设计复杂的架构和嵌入式值:

public static Schema schemaWithChildren() {
    var amount = new Field("amount", FieldType.nullable(new ArrowType.Decimal(19,4,128)), null);
    var currency = new Field("currency",FieldType.nullable(new ArrowType.Utf8()), null);
    var itemField = new Field("item", FieldType.nullable(new ArrowType.Utf8()), List.of(amount,currency));
 
    return new Schema(List.of(itemField));
}

基于上面的的Schema,我们将为我们的类创建一个DTO:

package com.gkatzioura.arrow;
 
import lombok.Builder;
import lombok.Data;
 
@Data
@Builder
public class DefaultArrowEntry {
 
    private String col1;
    private Integer col2;
 
}

我们的目标是将这些Java对象转换为Arrow字节流。

1. 使用分配器创建 DirectByteBuffer

这些缓冲区是 堆外的 。您确实需要释放所使用的内存,但是对于库用户而言,这是通过在分配器上执行 close() 操作来完成的。在我们的例子中,我们的类将实现 Closeable 接口,该接口将执行分配器关闭操作。

通过使用流api,数据将被流传输到使用Arrow格式提交的OutPutStream:

package com.gkatzioura.arrow;
 
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.WritableByteChannel;
import java.util.List;
 
import org.apache.arrow.memory.RootAllocator;
import org.apache.arrow.vector.IntVector;
import org.apache.arrow.vector.VarCharVector;
import org.apache.arrow.vector.VectorSchemaRoot;
import org.apache.arrow.vector.dictionary.DictionaryProvider;
import org.apache.arrow.vector.ipc.ArrowStreamWriter;
import org.apache.arrow.vector.util.Text;
 
import static com.gkatzioura.arrow.SchemaFactory.DEFAULT_SCHEMA;
 
public class DefaultEntriesWriter implements Closeable {
 
    private final RootAllocator rootAllocator;
    private final VectorSchemaRoot vectorSchemaRoot;//向量分配器创建:
 
    public DefaultEntriesWriter() {
        rootAllocator = new RootAllocator();
        vectorSchemaRoot = VectorSchemaRoot.create(DEFAULT_SCHEMA, rootAllocator);
    }
 
    public void write(List<DefaultArrowEntry> defaultArrowEntries, int batchSize, WritableByteChannel out) {
        if (batchSize <= 0) {
            batchSize = defaultArrowEntries.size();
        }
 
        DictionaryProvider.MapDictionaryProvider dictProvider = new DictionaryProvider.MapDictionaryProvider();
        try(ArrowStreamWriter writer = new ArrowStreamWriter(vectorSchemaRoot, dictProvider, out)) {
            writer.start();
 
            VarCharVector childVector1 = (VarCharVector) vectorSchemaRoot.getVector(0);
            IntVector childVector2 = (IntVector) vectorSchemaRoot.getVector(1);
            childVector1.reset();
            childVector2.reset();
 
            boolean exactBatches = defaultArrowEntries.size()%batchSize == 0;
            int batchCounter = 0;
 
            for(int i=0; i < defaultArrowEntries.size(); i++) {
                childVector1.setSafe(batchCounter, new Text(defaultArrowEntries.get(i).getCol1()));
                childVector2.setSafe(batchCounter, defaultArrowEntries.get(i).getCol2());
 
                batchCounter++;
 
                if(batchCounter == batchSize) {
                    vectorSchemaRoot.setRowCount(batchSize);
                    writer.writeBatch();
                    batchCounter = 0;
                }
            }
 
            if(!exactBatches) {
                vectorSchemaRoot.setRowCount(batchCounter);
                writer.writeBatch();
            }
 
            writer.end();
        } catch (IOException e) {
            throw new ArrowExampleException(e);
        }
    }
 
    @Override
    public void close() throws IOException {
        vectorSchemaRoot.close();
        rootAllocator.close();
    }
 
}

为了在Arrow上显示批处理的支持,已在函数中实现了简单的批处理算法。对于我们的示例,只需考虑将数据分批写入。

让我们深入了解上面代码功能:

向量分配器创建:

public DefaultEntriesToBytesConverter() {
    rootAllocator = new RootAllocator();
    vectorSchemaRoot = VectorSchemaRoot.create(DEFAULT_SCHEMA, rootAllocator);
}

然后在写入流时,实现并启动了Arrow流编写器

ArrowStreamWriter writer = new ArrowStreamWriter(vectorSchemaRoot, dictProvider, Channels.newChannel(out));
writer.start();

我们将数据填充向量,然后还重置它们,但让预分配的缓冲区 存在 :

VarCharVector childVector1 = (VarCharVector) vectorSchemaRoot.getVector(0);
IntVector childVector2 = (IntVector) vectorSchemaRoot.getVector(1);
childVector1.reset();
childVector2.reset();

写入数据时,我们使用 setSafe 操作。如果需要分配更多的缓冲区,应采用这种方式。对于此示例,此操作在每次写入时都完成,但是在考虑了所需的操作和缓冲区大小后可以避免:

childVector1.setSafe(i, new Text(defaultArrowEntries.get(i).getCol1()));
childVector2.setSafe(i, defaultArrowEntries.get(i).getCol2());

然后,将批处理写入流中:

vectorSchemaRoot.setRowCount(batchSize);
writer.writeBatch();

最后但并非最不重要的一点是,我们关闭了writer:

@Override
public void close() throws IOException {
    vectorSchemaRoot.close();
    rootAllocator.close();
}

以上就是JVM上高性能数据格式库包Apache Arrow入门和架构详解(Gkatziouras)的详细内容,更多关于Apache Arrow入门的资料请关注三水点靠木其它相关文章!

Servers 相关文章推荐
基于Nginx实现限制某IP短时间访问次数
Mar 31 Servers
Nginx配置80端口访问8080及项目名地址方法解析
Mar 31 Servers
解决使用了nginx获取IP地址都是127.0.0.1 的问题
Sep 25 Servers
教你快速构建一个基于nginx的web集群项目
Nov 27 Servers
Consul在linux环境的集群部署
Apr 08 Servers
Windows server 2012 R2 安装IIS服务器
Apr 29 Servers
利用nginx搭建RTMP视频点播、直播、HLS服务器
May 25 Servers
基于docker安装zabbix的详细教程
Jun 05 Servers
zabbix 代理服务器的部署与 zabbix-snmp 监控问题
Jul 15 Servers
本地搭建minio文件服务器(使用bat脚本启动)的方法
Jul 15 Servers
windows10 家庭版下FTP服务器搭建教程
Aug 05 Servers
ubuntu20.04虚拟机无法上网的问题及解决
Dec 24 Servers
Nginx配置Https安全认证的实现
May 26 #Servers
nginx部署多前端项目的几种方法
Nginx如何配置Http、Https、WS、WSS的方法步骤
May 11 #Servers
windows下快速安装nginx并配置开机自启动的方法
uwsgi+nginx代理Django无法访问静态资源的解决
May 10 #Servers
教你快速开启Apache SkyWalking的自监控
Apache Calcite 实现方言转换的代码
Apr 24 #Servers
You might like
PHP中CURL的CURLOPT_POSTFIELDS参数使用细节
2014/03/17 PHP
详解配置 Apache 服务器支持 PHP 文件的解析
2017/02/15 PHP
thinkphp 中的volist标签在ajax操作中的特殊性(推荐)
2018/01/15 PHP
jQuery 动画基础教程
2008/12/25 Javascript
onkeypress字符按键兼容所有浏览器使用介绍
2013/04/24 Javascript
jQuery焦点图切换特效插件封装实例
2013/08/18 Javascript
判断访客终端类型集锦
2015/06/05 Javascript
理解javascript中DOM事件
2015/12/25 Javascript
小心!AngularJS结合RequireJS做文件合并压缩的那些坑
2016/01/09 Javascript
AngularJS中的指令全面解析(必看)
2016/05/20 Javascript
浅谈javascript的闭包
2017/01/23 Javascript
javascript 中iframe高度自适应(同域)实例详解
2017/05/16 Javascript
JS轮播图实现简单代码
2021/02/19 Javascript
Vuejs实现购物车功能
2017/11/05 Javascript
[03:55]TI9战队采访——TNC Predator
2019/08/22 DOTA
python中尾递归用法实例详解
2015/04/28 Python
Python实现桶排序与快速排序算法结合应用示例
2017/11/22 Python
Flask框架通过Flask_login实现用户登录功能示例
2018/07/17 Python
python-opencv 将连续图片写成视频格式的方法
2019/01/08 Python
python+pyqt5实现24点小游戏
2019/01/24 Python
python multiprocessing模块用法及原理介绍
2019/08/20 Python
Python字符串大小写转换拼接删除空白
2019/09/19 Python
Django中使用haystack+whoosh实现搜索功能
2019/10/08 Python
Python ADF 单位根检验 如何查看结果的实现
2020/06/03 Python
python 爬取免费简历模板网站的示例
2020/09/27 Python
Python使用eval函数执行动态标表达式过程详解
2020/10/17 Python
值得收藏的HTML5资源(学习html5的朋友可以收藏下)
2010/07/20 HTML / CSS
毕业自我鉴定范文
2013/11/06 职场文书
商务主管岗位职责
2013/12/08 职场文书
教师考核评语
2014/04/28 职场文书
社区精神文明建设汇报材料
2014/08/17 职场文书
小区门卫的岗位职责
2014/09/26 职场文书
导游词开场白
2015/01/31 职场文书
结婚保证书(卖身契)
2015/02/26 职场文书
与Windows10相比Windows11有哪些改进?值不值得升级?
2021/11/21 数码科技
PYTHON 使用 Pandas 删除某列指定值所在的行
2022/04/28 Python