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


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

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

Flight 最初专注于通过 gRPC(谷歌流行的基于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 服务器支持几种基本类型的请求:

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

我们利用 gRPC 优雅的“双向”流式传输支持(建立在 HTTP/2 流式传输之上),允许客户端和服务器在请求服务期间同时向彼此发送数据和元数据。

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

Flight Simple Architecture

优化 gRPC 上的数据吞吐量

虽然使用像 gRPC 这样的通用消息传递库有许多特定的好处(超越了显而易见的好处,例如利用了谷歌在解决此问题上所做的所有工程工作),但仍需要做一些工作来提高传输大型数据集的性能。例如,许多类型的 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++ 数据吞吐量基准测试中,在未启用 TLS 的情况下,我们在本地主机上看到了超过 2-3GB/s 的端到端 TCP 吞吐量。此基准测试显示大约 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 会返回一个 **端点 (endpoints)** 列表,每个端点包含一个服务器位置和一个 **票据 (ticket)**,该票据应发送给该服务器以在 DoGet 请求中获取完整数据集的一部分。要访问整个数据集,所有端点都必须被消费。虽然 Flight 流不一定是排序的,但我们提供了应用程序定义的元数据,可用于序列化排序信息。

这种多端点模式有许多好处:

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

以下是具有分离服务角色的多节点架构示例图

Flight Complex Architecture

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

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

因此,Flight 服务可以可选地定义由 DoAction RPC 执行的“动作 (actions)”。Action 请求包含要执行的 Action 的名称以及可选的序列化数据,其中包含其他所需信息。Action 的结果是操作结果的不透明二进制流。

一些示例 Action

  • 元数据发现,超出了内置 ListFlights RPC 提供的能力范围
  • 设置特定于会话的参数和设置

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

加密和认证

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

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

中间件和跟踪

gRPC 具有“拦截器 (interceptors)”的概念,这使我们能够开发开发人员定义的“中间件 (middleware)”,为传入和传出请求提供检测或遥测。OpenTracing 就是这样一个用于此类检测的框架。

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

gRPC,但不局限于 gRPC

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

虽然我们认为将 gRPC 用于 Flight 服务器的“命令”层是合理的,但我们也可能希望支持除 TCP 之外的数据传输层,例如 RDMA。尽管这需要一些设计和开发工作才能实现,但其思想是 gRPC 可用于协调可能在非 TCP 协议上执行的 get 和 put 传输。

入门和后续计划

Flight 用户的文档仍在完善中,但这些库本身对于在未来一年内能容忍一些小的 API 或协议更改的 Beta 用户来说已经足够成熟了。

试验 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 在自定义数据服务中的特定应用相关的细节。