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 实现。 这些库适用于对 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
请求的单个服务器组成。

优化 gRPC 上的数据吞吐量
虽然使用像 gRPC 这样的通用消息传递库除了显而易见的好处之外还有许多具体的好处(利用 Google 在此问题上所做的所有工程),但还需要一些工作来提高传输大型数据集的性能。 例如,许多 gRPC 用户只处理相对较小的消息。
使用 gRPC 的最佳方式是在 Protocol Buffers(又名“Protobuf”) .proto
文件中定义服务。 用于 gRPC 的 Protobuf 插件会生成 gRPC 服务存根,您可以使用这些存根来实现您的应用程序。 RPC 命令和数据消息使用 Protobuf 线路格式 进行序列化。 因为我们使用“普通的 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++ 数据吞吐量基准测试中,我们在 localhost 上看到超过 2-3GB/s 的端到端 TCP 吞吐量,并且未启用 TLS。 此基准测试显示大约 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
“计划”请求的服务可以将工作委派给同级服务,以利用数据局部性或仅用于帮助进行负载平衡。 - 分布式集群中的节点可以承担不同的角色。 例如,一部分节点可能负责计划查询,而其他节点专门满足数据流(
DoGet
或DoPut
)请求。
以下是具有拆分服务角色的多节点架构的示例图

操作:使用应用程序业务逻辑扩展 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 以外的协议上执行的 get 和 put 传输。
入门和下一步
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 在自定义数据服务中的特定应用程序相关的详细信息。