基于Apache Hudi + MinIO 构建流式数据湖

时间:2022-10-11 16:09:03

Apache Hudi 是一个流式数据湖平台,将核心仓库和数据库功能直接引入数据湖。Hudi 不满足于将自己称为 Delta 或 Apache Iceberg 之类的开放文件格式,它提供表、事务、更新/删除、高级索引、流式摄取服务、数据聚簇/压缩优化和并发性。Hudi 于 2016 年推出,牢牢扎根于 Hadoop 生态系统,解释了名称背后的含义:Hadoop Upserts Deletes and Incrementals。它是为管理 HDFS 上大型分析数据集的存储而开发的。Hudi 的主要目的是减少流数据摄取过程中的延迟。

基于Apache Hudi + MinIO 构建流式数据湖

随着时间的推移,Hudi 已经发展到使用云存储和对象存储,包括 MinIO。Hudi 从 HDFS 的转变与世界的大趋势齐头并进,将传统的 HDFS 抛在脑后,以实现高性能、可扩展和云原生对象存储。Hudi 承诺提供优化,使 Apache Spark、Flink、Presto、Trino 和其他的分析工作负载更快,这与 MinIO 对大规模云原生应用程序性能的承诺非常吻合。在生产中使用 Hudi 的公司包括 Uber、亚马逊、字节跳动和 Robinhood。这些是世界上一些最大的流式数据湖。Hudi 在这个用例中的关键在于它提供了一个增量数据处理栈,可以对列数据进行低延迟处理。通常系统使用 Apache Parquet 或 ORC 等开放文件格式将数据写入一次,并将其存储在高度可扩展的对象存储或分布式文件系统之上。Hudi 作为数据平面来摄取、转换和管理这些数据。Hudi 使用 Hadoop FileSystem API[7] 与存储交互,该 API 与从 HDFS 到对象存储到内存文件系统的各种实现兼容。

Hudi 文件格式

Hudi 使用基本文件和增量日志文件来存储对给定基本文件的更新/更改。基本文件可以是 Parquet(列)或 HFile(索引),增量日志保存为 Avro(行),因为在发生更改时记录对基本文件的更改是有意义的。Hudi 将给定基本文件的所有更改编码为一系列块。块可以是数据块、删除块或回滚块。这些块被合并以便派生更新的基础文件。这种编码还创建了一个独立的日志。

基于Apache Hudi + MinIO 构建流式数据湖

表格式由表的文件布局、表的模式(Schema)和跟踪表更改的元数据组成。Hudi 强制执行模式写入,与强调流处理一致,以确保管道不会因非向后兼容的更改而中断。Hudi 将给定表/分区的文件分组在一起,并在记录键和文件组之间进行映射。如上所述,所有更新都记录到特定文件组的增量日志文件中。这种设计比 Hive ACID 更高效,后者必须将所有数据记录与所有基本文件合并以处理查询。Hudi 的设计预计基于键的快速更新插入和删除,因为它使用文件组的增量日志,而不是整个数据集。

基于Apache Hudi + MinIO 构建流式数据湖

时间线对于理解Hudi至关重要,因为它是所有 Hudi 表元数据的真实事件日志的来源。时间线存储在 .hoodie 文件夹中,在我们的例子中是存储桶。事件将保留在时间线上直到它们被删除。整个表和文件组都存在时间线,通过将增量日志应用于原始基本文件,可以重建文件组。为了优化频繁的写入/提交,Hudi 的设计使元数据相对于整个表的大小保持较小。时间线上的新事件被保存到内部元数据表中,并作为一系列读取时合并的表实现,从而提供低写入放大。因此,Hudi 可以快速吸收元数据的快速变化。此外元数据表使用 HFile 基本文件格式,通过一组索引键查找进一步优化性能,避免读取整个元数据表。作为表一部分的所有物理文件路径都包含在元数据中,以避免昂贵且耗时的云文件列表。

Hudi写入

Hudi 写入架构具有 ACID 事务支持的高性能写入层,可实现非常快速的增量更改,例如更新和删除。典型的 Hudi 架构依赖 Spark 或 Flink 管道将数据传递到 Hudi 表。Hudi 写入路径经过优化,比简单地将 Parquet 或 Avro 文件写入磁盘更有效。Hudi 分析写入操作并将它们分类为增量操作(insert, upsert, delete)或批量操作(insert_overwrite, insert_overwrite_table, delete_partition, bulk_insert),然后应用必要的优化[8]。Hudi 写入器还负责维护元数据。对于每条记录,都会写入该记录唯一的提交时间和序列号(这类似于 Kafka 偏移量),从而可以派生记录级别的更改。用户还可以在传入数据流中指定事件时间字段,并使用元数据和 Hudi 时间线跟踪它们。这可以显着改进流处理,因为 Hudi 包含每个记录的到达时间和事件时间,从而可以为复杂的流处理管道构建强大的水印[9]。

Hudi读取

写入器和读取器之间的快照隔离允许从所有主要数据湖查询引擎(包括 Spark、Hive、Flink、Prest、Trino 和 Impala)中一致地查询表快照。与 Parquet 和 Avro 一样,Hudi 表可以被 Snowflake[10] 和 SQL Server[11] 等作为外部表读取。Hudi 读取器非常轻量,尽可能使用特定于引擎的向量化读取器和缓存,例如 Presto 和 Spark。当 Hudi 必须为查询合并基本文件和日志文件时,Hudi 使用可溢出映射和延迟读取等机制提高合并性能,同时还提供读取优化查询。Hudi 包含许多非常强大的增量查询功能,元数据是其中的核心,允许将大型提交作为较小的块使用,并完全解耦数据的写入和增量查询。通过有效使用元数据,时间旅行非常容易实现,其只是另一个具有定义起点和终点的增量查询。Hudi 在任何给定时间点以原子方式将键映射到单个文件组,支持 Hudi 表上的完整 CDC 功能。正如上面 Hudi 写入器部分所讨论的,每个表都由文件组组成,每个文件组都有自己的自包含元数据。

Hudi核心特性

Hudi 最大的优势在于它摄取流式和批处理数据的速度。通过提供 upsert 功能,Hudi 执行任务的速度比重写整个表或分区快几个数量级。为了利用 Hudi 的摄取速度,数据湖库需要一个具有高 IOPS 和吞吐量的存储层。MinIO 的可扩展性和高性能的结合正是 Hudi 所需要的。MinIO 能够满足为实时企业数据湖提供动力所需的性能——最近的一项基准测试[12]在 GET 上实现了 325 GiB/s (349 GB/s),在 PUT 上实现了 165 GiB/s (177 GB/s) 32 个现成的 NVMe SSD 节点。活跃的企业 Hudi 数据湖存储大量小型 Parquet 和 Avro 文件。MinIO 包括许多小文件优化[13],可实现更快的数据湖。小对象与元数据一起保存,减少了读取和写入小文件(如 Hudi 元数据和索引)所需的 IOPS。模式(Schema) 是每个 Hudi 表的关键组件。Hudi 可以强制执行模式,也可以允许模式演变,以便流数据管道可以适应而不会中断。此外Hudi 强制执行 Schema-on-Writer 以确保更改不会破坏管道。Hudi 依靠 Avro 来存储、管理和发展表的模式。Hudi 为数据湖提供 ACID 事务保证。Hudi 确保原子写入:以原子方式向时间线提交提交,并给出一个时间戳,该时间戳表示该操作被视为发生的时间。Hudi 隔离了写入器、表 和 读取器进程之间的快照,因此每个进程都对表的一致快照进行操作。Hudi 通过写入器之间的乐观并发控制 (OCC) 以及表服务和写入器之间以及多个表服务之间的基于 MVCC 的非阻塞并发控制来完善这一点。