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 重定向到系统维护页面
Jun 08 Servers
Nginx反向代理学习实例教程
Oct 24 Servers
Apache Hudi数据布局黑科技降低一半查询时间
Mar 31 Servers
Kubernetes控制节点的部署
Apr 01 Servers
CentOS7和8下安装Maven3.8.4
Apr 07 Servers
阿里云k8s服务升级时502错误 springboot项目应用
Apr 09 Servers
Nginx利用Logrotate实现日志分割
May 20 Servers
Zabbix对Kafka topic积压数据监控的问题(bug优化)
Jul 07 Servers
Nginx文件已经存在全局反向代理问题排查记录
Jul 15 Servers
windows server2016安装oracle 11g的图文教程
Jul 15 Servers
centos环境下nginx高可用集群的搭建指南
Jul 23 Servers
阿里云服务器(windows)手动部署FTP站点详细教程
Aug 05 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和Shell实现检查SAMBA与NFS Server是否存在
2015/01/07 PHP
php字符串按照单词进行反转的方法
2015/03/14 PHP
PHP并发多进程处理利器Gearman使用介绍
2016/05/16 PHP
PHP实现数据库的增删查改功能及完整代码
2018/04/18 PHP
Javascript 文件夹选择框的两种解决方案
2009/07/01 Javascript
同域jQuery(跨)iframe操作DOM(实例讲解)
2013/12/19 Javascript
浅谈Javascript如何实现匀速运动
2014/12/19 Javascript
基于javascript的COOkie的操作实现只能点一次
2014/12/26 Javascript
javascript 获取浏览器版本
2015/01/21 Javascript
基于JavaScript实现手机短信按钮倒计时(超简单)
2015/12/30 Javascript
jquery插件EasyUI中form表单提交实例分享
2016/01/11 Javascript
JavaScript面向对象程序设计教程
2016/03/29 Javascript
js判断所有表单项不为空则提交表单的实现方法
2016/09/09 Javascript
详解angularJs中自定义directive的数据交互
2017/01/13 Javascript
JS日程管理插件FullCalendar中文说明文档
2017/02/06 Javascript
js获取地址栏参数的两种方法
2017/06/27 Javascript
详解webpack3如何正确引用并使用jQuery库
2017/08/26 jQuery
浅谈React Native 中组件的生命周期
2017/09/08 Javascript
Vue.set()实现数据动态响应的方法
2018/02/07 Javascript
javascript实现搜索筛选功能实例代码
2020/11/12 Javascript
pandas对指定列进行填充的方法
2018/04/11 Python
使用tensorflow实现线性svm
2018/09/07 Python
Python实现的简单线性回归算法实例分析
2018/12/26 Python
Spring Cloud Feign高级应用实例详解
2019/12/10 Python
解决Tensorflow占用GPU显存问题
2020/02/03 Python
Python 字典中的所有方法及用法
2020/06/10 Python
爱游人:Travelliker
2017/09/05 全球购物
英语专业毕业生自荐信
2013/10/28 职场文书
最新的咖啡店创业计划书
2013/12/30 职场文书
学校周年庆活动方案
2014/08/22 职场文书
班子群众路线教育实践个人对照检查材料思想汇报
2014/09/30 职场文书
2014年会计人员工作总结
2014/12/10 职场文书
2014企业年终工作总结
2014/12/23 职场文书
2015年药店店长工作总结
2015/04/29 职场文书
百家讲坛观后感
2015/06/12 职场文书
同步小康驻村工作简报
2015/07/20 职场文书