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


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

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

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

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

在 0.15.0 Apache Arrow 版本中,我们已经准备好使用 C++(带有 Python 绑定)和 Java 实现的 Flight。 这些库适用于 Beta 用户,这些用户在我们在 Flight 内部继续完善一些底层细节时,可以适应 API 或协议更改。

动机

许多人经历过通过网络访问大型数据集带来的痛苦。 有许多不同的传输协议和工具可用于从远程数据服务读取数据集,例如 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 这样的通用消息传递库除了明显的优势(利用 Google 在该问题上所做的所有工程)之外还有许多具体的好处,但仍需要做一些工作来提高传输大型数据集的性能。 例如,许多类型的 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 的情况下,在本地主机上看到端到端 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

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

gRPC,但不限于 gRPC

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

虽然我们认为将 gRPC 用于 Flight 服务器的“命令”层是有意义的,但我们可能希望支持 TCP 以外的数据传输层,例如 RDMA(远程直接内存访问)。虽然实现这一点需要一些设计和开发工作,但其理念是 gRPC 可用于协调 get 和 put 传输,这些传输可以使用 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 在自定义数据服务中的特定应用相关的细节。