Apache Arrow Flight 简介:一个用于快速数据传输的框架


发布日期 2019 年 10 月 13 日
作者 Wes McKinney (wesm)
翻译 日本語

在过去 18 个月中,Apache Arrow 社区一直致力于设计和实现 Flight,这是一个新的通用客户端-服务器框架,旨在简化通过网络接口高速传输大型数据集。

Flight 最初专注于通过 gRPC 优化 Arrow 列式格式(即“Arrow 记录批次”)的传输,gRPC 是谷歌流行的基于 HTTP/2 的通用 RPC 库和框架。虽然我们专注于与 gRPC 集成,但作为开发框架,Flight 并不打算仅限于 gRPC。

Flight 与其他数据传输框架最大的区别之一是并行传输,它允许数据同时流式传输到服务器集群或从服务器集群传输。这使得开发人员可以更容易地创建可扩展的数据服务,以服务不断增长的客户端群。

在 Apache Arrow 0.15.0 版本中,我们提供了 C++(带有 Python 绑定)和 Java 中可立即使用的 Flight 实现。这些库适用于那些对 API 或协议更改感到满意的 Beta 用户,因为我们将继续完善 Flight 内部的一些底层细节。

动机

许多人都经历过通过网络访问大型数据集的痛苦。有许多不同的传输协议和工具可以从远程数据服务读取数据集,例如 ODBC 和 JDBC。在过去的 10 年中,CSV、Avro 和 Parquet 等格式的基于文件的数据仓库变得流行起来,但这也会带来挑战,因为原始数据在反序列化之前必须传输到本地主机。

自 Apache Arrow 成立以来我们所做的工作为加速数据传输带来了令人兴奋的前景,具体体现在以下几个方面:Arrow 列式格式 具有以下关键特性,可以帮助我们实现这一目标:

  • 它是一种“在线”表格数据表示,接收时无需反序列化。
  • 它的自然模式是“流式批次”,较大的数据集一次传输一批行(在 Arrow 术语中称为“记录批次”)。在这篇文章中,我们将讨论“数据流”,它们是使用项目二进制协议的 Arrow 记录批次序列。
  • 该格式与语言无关,目前已在 11 种语言中提供库支持,并且还在不断增加。

ODBC 等标准协议的实现通常会实现自己的自定义在线二进制协议,这些协议必须在每个库的公共接口之间进行编组。ODBC 或 JDBC 库的性能因情况而异。

我们对 Flight 的设计目标是为数据服务创建一个新协议,该协议使用 Arrow 列式格式作为在线数据表示和呈现给开发人员的公共 API。通过这样做,我们减少或消除了与数据传输相关的序列化成本,并提高了分布式数据系统的整体效率。此外,两个已经将 Apache Arrow 用于其他目的的系统可以以极高的效率相互通信数据。

Flight 基础知识

Arrow Flight 库提供了一个开发框架,用于实现可以发送和接收数据流的服务。Flight 服务器支持几种基本的请求类型:

  • 握手:一个简单的请求,用于确定客户端是否已授权,在某些情况下,用于建立一个实现定义的会话令牌,以便在将来的请求中使用
  • ListFlights:返回可用数据流的列表
  • GetSchema:返回数据流的模式
  • GetFlightInfo:返回感兴趣数据集的“访问计划”,可能需要使用多个数据流。此请求可以接受包含自定义序列化命令,例如,您的特定应用程序参数。
  • DoGet:向客户端发送数据流
  • DoPut:从客户端接收数据流
  • DoAction:执行实现特定的操作并返回任何结果,即广义函数调用
  • ListActions:返回可用操作类型的列表

我们利用 gRPC 优雅的“双向”流支持(基于 HTTP/2 流)来允许客户端和服务器在服务请求的同时相互发送数据和元数据。

一个简单的 Flight 设置可能包括一个单一服务器,客户端连接到该服务器并发出 DoGet 请求。

Flight Simple Architecture

优化 gRPC 上的数据吞吐量

虽然使用像 gRPC 这样的通用消息库除了显而易见的好处之外还有许多具体的好处(利用谷歌在问题上所做的所有工程),但仍需要做一些工作来提高传输大型数据集的性能。例如,许多 gRPC 用户只处理相对较小的消息。

使用 gRPC 的最佳支持方式是在 Protocol Buffers(又称“Protobuf”).proto 文件中定义服务。gRPC 的 Protobuf 插件会生成 gRPC 服务存根,您可以用来实现您的应用程序。RPC 命令和数据消息使用 Protobuf 传输格式进行序列化。由于我们使用“ vanilla gRPC 和 Protocol Buffers”,因此不了解 Arrow 列式格式的 gRPC 客户端仍然可以与 Flight 服务交互并以不透明的方式处理 Arrow 数据。

Flight 中主要的与数据相关的 Protobuf 类型称为 FlightData。通常,读写 Protobuf 消息并非没有开销,因此我们在 C++ 和 Java 中都对 gRPC 实施了一些底层优化,以实现以下目的:

  • 生成 FlightData 的 Protobuf 传输格式,包括正在发送的 Arrow 记录批次,而无需经过任何中间内存复制或序列化步骤。
  • FlightData 的 Protobuf 表示中重建 Arrow 记录批次,无需任何内存复制或反序列化。事实上,我们拦截了编码数据有效载荷,而没有让 Protocol Buffers 库触及它们。

从某种意义上说,我们是“鱼与熊掌兼得”。采用这些优化的 Flight 实现将具有更好的性能,而简单的 gRPC 客户端仍然可以与 Flight 服务通信并使用 Protobuf 库反序列化 FlightData(尽管会牺牲一些性能)。

就绝对速度而言,在我们的 C++ 数据吞吐量基准测试中,我们看到在未启用 TLS 的 localhost 上,端到端 TCP 吞吐量超过 2-3GB/s。该基准测试显示在约 4 秒内传输了约 12 GB 的数据。

$ ./arrow-flight-benchmark --records_per_stream 100000000
Bytes read: 12800000000
Nanos: 3900466413
Speed: 3129.63 MB/s

由此我们可以得出结论,Flight 和 gRPC 的机制带来的开销相对较小,这表明 Flight 的许多实际应用将受限于网络带宽。

横向扩展性:并行和分区数据访问

许多分布式数据库系统都采用一种架构模式,其中客户端请求的结果通过“协调器”路由并发送给客户端。除了在数据传输到客户端途中多次传输数据集的明显效率问题之外,它还为访问非常大的数据集带来了可伸缩性问题。

我们希望 Flight 能够使系统创建水平可扩展的数据服务,而无需处理此类瓶颈。客户端使用 GetFlightInfo RPC 对数据集的请求返回一个端点列表,每个端点都包含一个服务器位置和一个票据,用于在 DoGet 请求中发送给该服务器以获取完整数据集的一部分。要访问整个数据集,必须使用所有端点。虽然 Flight 流不一定是有序的,但我们提供应用程序定义的元数据,可用于序列化排序信息。

这种多端点模式有许多优点:

  • 客户端可以并行读取端点。
  • 处理 GetFlightInfo“规划”请求的服务可以将工作委托给兄弟服务,以利用数据局部性或仅帮助负载均衡。
  • 分布式集群中的节点可以承担不同的角色。例如,部分节点可能负责规划查询,而其他节点则专门处理数据流(DoGetDoPut)请求。

下面是一个具有拆分服务角色的多节点架构示例图

Flight Complex Architecture

操作:使用应用程序业务逻辑扩展 Flight

尽管 GetFlightInfo 请求支持在请求数据集时发送不透明的序列化命令,但客户端可能需要能够请求服务器执行其他类型的操作。例如,客户端可能请求将特定数据集“固定”在内存中,以便其他客户端的后续请求能够更快地得到服务。

因此,Flight 服务可以选择性地定义“操作”,这些操作由 DoAction RPC 执行。操作请求包含正在执行的操作的名称以及包含进一步所需信息的可选序列化数据。操作的结果是 gRPC 的不透明二进制结果流。

一些操作示例:

  • 元数据发现,超越了内置 ListFlights RPC 提供的功能
  • 设置会话特定参数和设置

请注意,服务器不需要实现任何操作,并且操作不必返回结果。

加密与认证

Flight 通过 gRPC 内置的 TLS / OpenSSL 功能支持开箱即用的加密。

对于身份验证,客户端和服务器都有可扩展的身份验证处理程序,允许简单的身份验证方案(如用户和密码)以及更复杂的身份验证(如 Kerberos)。Flight 协议内置了 BasicAuth,因此无需自定义开发即可开箱即用地实现用户/密码身份验证。

中间件和追踪

gRPC 具有“拦截器”的概念,这使我们能够开发开发人员定义的“中间件”,为传入和传出请求提供仪器或遥测。其中一个这样的仪器框架是 OpenTracing

请注意,中间件功能是项目最新领域之一,目前仅在项目的主分支中可用。

gRPC,但不仅限于 gRPC

我们使用符合 RFC 3986 规范的 URI 为 DoGet 请求指定服务器位置。例如,TLS 安全的 gRPC 可以指定为 grpc+tls://$HOST:$PORT

虽然我们认为使用 gRPC 作为 Flight 服务器的“命令”层是合理的,但我们可能希望支持 TCP 以外的数据传输层,例如 RDMA。虽然需要一些设计和开发工作才能实现这一点,但我们的想法是 gRPC 可以用于协调可能在 TCP 以外的协议上执行的获取和放置传输。

入门和下一步

Flight 用户的文档正在开发中,但库本身已经足够成熟,可供 Beta 用户使用,他们可以容忍未来一年中一些轻微的 API 或协议更改。

尝试 Flight 最简单的方法之一是使用 Python API,因为自定义服务器和客户端可以完全在 Python 中定义,无需任何编译。您可以在 Arrow 代码库中看到一个 Python 中的 Flight 客户端和服务器示例

在实际应用中,Dremio 开发了一个 基于 Arrow Flight 的 连接器,该连接器已被证明比 ODBC 提供 20-50 倍的性能提升。对于 Apache Spark 用户,Arrow 贡献者 Ryan Murray 创建了一个 数据源实现,用于连接到启用 Flight 的端点。

至于 Flight 的“下一步”,支持非 gRPC(或非 TCP)数据传输可能是一个有趣的研究和开发方向。 Flight 的大部分工作将是创建面向用户的启用 Flight 的服务。由于 Flight 是一个开发框架,我们预计面向用户的 API 将利用一层 API 封装,隐藏许多通用的 Flight 细节以及与 Flight 在自定义数据服务中的特定应用相关的细节。