Apache Arrow Flight 简介:高速数据传输框架
发布日期 2019 年 10 月 13 日
作者 Wes McKinney (wesm)
译文 原文(英文)
在过去一年半的时间里,Apache Arrow 社区一直在设计和实现 Flight,这是一个新的客户端-服务器框架,用于实现高速数据传输。Flight 使得在网络上传输大型数据集变得轻而易举。Flight 并非为特定用途而设计,因此可用于广泛的应用场景。
Flight 的实现首先专注于使用 gRPC 优化 Arrow 列式格式(即“Arrow 记录批次”)的传输。gRPC 是 Google 开发的基于 HTTP/2 的 RPC 库和框架,并被广泛使用。gRPC 也并非为特定用途而设计,可用于广泛的应用场景。尽管迄今为止 Flight 的实现一直专注于基于 gRPC,但我们不希望它仅限于 gRPC。
Flight 与其他数据传输框架的一个主要区别是其并行传输功能。客户端和服务器集群之间可以同时以流式方式传输数据。此功能使开发可扩展的数据服务变得容易。可扩展的数据服务是指即使客户端数量增加也能正常运行的服务。
Apache Arrow 0.15.0 版本已提供 C++(带 Python 绑定)和 Java 中的 Flight。目前面向 Beta 用户。Beta 用户是指能够适应 Flight 内部低级别改进导致 API 或协议变更的用户。
动机
许多人在通过网络访问大型数据集方面遇到了困难。存在许多用于从远程数据服务读取数据集的传输协议和工具。例如,ODBC 和 JDBC。在过去的十年中,以文件为基础存储数据变得越来越普遍。在这种情况下,CSV、Avro 和 Parquet 等格式经常被使用。然而,这种方法存在一个问题,即必须在反序列化之前将原始数据传输到本地主机。
Apache Arrow 的早期工作可以通过多种方式加速数据传输。Arrow 列式格式 具有以下重要功能:
- 它是表格数据的“传输专用”表示。这种表示在数据接收端无需反序列化。
- 它具有标准的“批次流式传输”模式。在此模式下,大型数据集会分多行批量传输。(在 Arrow 术语中称为“记录批次”。)本文中,我们讨论“数据流”。数据流是指使用 Apache Arrow 项目的二进制协议的一系列 Arrow 记录批次。
- 此格式独立于编程语言。此格式目前支持 11 种编程语言。支持的编程语言数量还在不断增加。
ODBC 等标准协议的每个实现通常都会实现各自的传输二进制协议。这些协议必须与每个库的公共接口表示进行相互转换。ODBC/JDBC 库的性能有时会大相径庭。
我们设计 Flight 的目标是为数据服务创建一个新协议。此协议在传输数据表示和开发人员公开 API 中都使用 Arrow 列式格式。通过这种方式,可以降低数据传输相关的序列化成本,并提高整个分布式数据系统的效率。此外,在已经将 Apache Arrow 用于其他用途的系统之间,可以非常高效地交换数据。
Flight 的基础
Arrow Flight 库提供了一个开发人员框架,用于实现能够发送和接收数据流的服务。Flight 服务器支持以下基本请求:
- 握手 (Handshake):一个简单的请求,用于确认客户端是否已通过身份验证。在某些情况下,它会为后续请求建立一个实现定义的会话令牌。
- 列出航班 (ListFlights):返回可用数据流的列表。
- 获取模式 (GetSchema):返回数据流的模式。
- 获取航班信息 (GetFlightInfo):返回目标数据集的“访问计划”。可能需要消费多个数据流。此请求可以包含序列化的自定义命令。例如,它可以包含应用程序特定的参数。
- 执行获取 (DoGet):向客户端发送数据流。
- 执行放置 (DoPut):从客户端接收数据流。
- 执行操作 (DoAction):执行实现依赖的操作并返回结果。换句话说,这是一个通用的函数调用。
- 列出操作 (ListActions):返回可用操作的类型。
利用 gRPC 的“双向”流式传输支持(在 HTTP/2 流式传输 上实现),即使在请求处理期间,客户端和服务器之间也可以交换数据和元数据。
简单的 Flight 配置由一台服务器和连接到该服务器并发出 DoGet 请求的客户端组成。
gRPC 数据吞吐量的优化
使用 gRPC 等通用消息库具有许多优点。通用库已经解决了许多问题。在 gRPC 的情况下,Google 已经解决了许多问题。然而,为了提高大型数据集的传输性能,需要改进一些处理。因为许多 gRPC 用户只处理相对较小的消息。
使用 gRPC 最受支持的方法是使用 Protocol Buffers(有时称为“Protobuf”)的 .proto 文件定义服务。gRPC 的 Protobuf 插件会生成 gRPC 服务的存根。您可以使用此存根实现应用程序。RPC 命令和数据消息使用 Protobuf 线格式 进行序列化。Flight 使用“普通的 gRPC 和 Protocol Buffers”,因此即使不知道 Arrow 列式格式的 gRPC 客户端也可以与 Flight 服务交互,并且可以处理而无需关心 Arrow 数据的内容。
Flight 中主要的数据相关 Protobuf 类型称为 FlightData。通常,Protobuf 消息的读写成本很高。因此,C++ 和 Java 都为 gRPC 实现了一些低级别的优化,例如:
-
为
FlightData生成 Protobuf 线格式。FlightData包含要发送的 Arrow 记录批次,但没有任何内存复制或序列化处理。 - 可以从 Protobuf 表示的
FlightData重建 Arrow 记录批次,而无需内存复制或反序列化处理。实际上,我们阻止 Protocol Buffers 库接触编码后的数据负载。
这意味着我们试图在两者之间取得平衡:既要使用 Protobuf 又要消除 Protobuf 消息解析的开销。Flight 实现通过上述优化来加速。原始 gRPC 客户端也可以与 Flight 服务交互,但原始 gRPC 客户端没有这些优化,因此会使用 Protobuf 库反序列化 FlightData。因此,使用原始 gRPC 客户端会导致性能有所下降。
Flight C++ 实现的数据吞吐量基准测试结果显示,在本地主机上运行的服务器和客户端之间的 TCP 吞吐量超过 2-3GB/s。但是,TLS 处于禁用状态。此基准测试表明可以在大约 4 秒内传输 12GB 数据。
$ ./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 流。可以处理任何端点的 Flight 流。但是,我们提供了按特定顺序处理的机制。这种机制是可以使用应用程序特定的元数据。顺序信息可以在元数据中表示。
这种多端点模式有许多优点。
- 多个客户端可以从多个端点并行读取数据。
-
提供
GetFlightInfo“计划”请求的服务可以将处理委托给兄弟服务。这可以获得数据局部性优势,或者简单地更容易进行负载均衡。 - 分布式集群中的节点可以承担不同的角色。例如,集群中的某些节点可能负责查询计划。而其他节点可能只处理数据流请求(
DoGet或DoPut)。
下图是一个服务角色分离的多节点架构示例。
操作:使用应用程序特定逻辑扩展 Flight
GetFlightInfo 请求可以在请求数据集时发送序列化命令而不关心其内容,但客户端可能需要请求服务器执行除发送和接收数据流之外的操作。例如,客户端可能请求将特定数据集“固定”在内存中。通过固定,可以快速处理其他客户端的后续请求。
Flight 服务可以额外定义“操作”。可以使用 DoAction RPC 执行操作。操作请求包含要执行的操作名称和可选的附加信息。操作结果是一个 gRPC 流。此 gRPC 流中可以包含任意结果。
以下是一些操作示例:
- 查找元数据的操作。尽管内置的
ListFlightsRPC 也提供此功能,但如果ListFlights的功能不足,则可以通过操作实现。 - 设置会话特定参数的操作。
请注意,服务器无需实现任何操作。此外,操作也无需返回结果。
加密和认证
Flight 内置支持加密。它使用 gRPC 内置的 TLS/OpenSSL 功能。
客户端和服务器端都有可扩展的认证处理程序。此认证处理程序可用于简单的认证方案,如用户名和密码,或复杂的认证方案,如 Kerberos。Flight 协议内置了 BasicAuth 功能。因此,无需额外开发即可实现用户名和密码认证。
中间件和追踪
gRPC 有一个“拦截器”的概念。拦截器允许开发人员开发定义的“中间件”。中间件可以介入传入和传出的请求。执行此类处理的框架有 OpenTracing。
中间件功能是最近添加到 Flight 的功能。因此,目前仅在 master 分支中可用。
使用 gRPC 但不限于 gRPC
在 DoGet 请求中指定服务器位置的方法使用符合 RFC 3986 的 URI。例如,使用 TLS 的 gRPC 指定为 grpc+tls://$HOST:$PORT。
我们认为在 Flight 服务器的“命令”层中使用 gRPC 是合理的,但也可能希望支持非 TCP 数据传输层,如 RDMA。虽然需要设计和开发时间,但即使在非 TCP 协议上进行数据传输时,也可能使用 gRPC。
入门和展望
Flight 用户的文档正在创建中。但是,该库对于 Beta 用户来说已经足够可用。Beta 用户是指能够承受未来一年可能发生的轻微 API 和协议更改的用户。
尝试 Flight 的简单方法是使用 Python API。因为自定义服务器和自定义客户端都可以完全用 Python 定义。无需编译任何东西。可以参考 Arrow 代码中的 Python 中的 Flight 客户端和服务器示例。
也有实际使用的例子。Dremio 开发了一个 基于 Arrow Flight 的 连接器。此连接器显示出 比 ODBC 高 20-50 倍的性能。Arrow 的贡献者 Ryan Murray 为 Apache Spark 用户创建了一个连接到 Flight 兼容端点的 数据源实现。
最后,谈谈未来的发展。我们可能会研究和开发是否支持非 gRPC(或非 TCP)数据传输。随着 Flight 的发展,用户可用的 Flight 兼容服务将越来越多。Flight 是一个开发框架,因此我们打算让用户使用的 API 仅限于高级 API。高级 API 将隐藏 Flight 的详细信息以及与特定 Flight 应用程序相关的详细信息。