什么是Hudi?
Apache Hudi(简称:Hudi)使得您能在hadoop兼容的存储之上存储大量数据,同时它还提供两种原语,使得除了经典的批处理之外,还可以在数据湖上进行流处理。这两种原语分别是:
- Update/Delete Records (how do I change records in a table?) # 更新/删除记录:Hudi使用细粒度的文件/记录级别索引来支持Update/Delete记录,同时还提供写操作的事务保证。查询会处理最后一个提交的快照,并基于此输出结果。
- Change Streams (how do I fetch records that changed?) # 变更流:
Hudi对获取数据变更提供了一流的支持:可以从给定的时间点获取给定表中已updated/inserted/deleted的所有记录的增量流,并解锁新的查询姿势(类别)
Hudi基础架构如下:
Hudi特色
- 通过快速,可插入的索引支持Upsert。
- 通过回滚支持以原子方式发布数据。
- 编写器和查询之间的快照隔离。
- 用于数据恢复的保存点。
- 使用统计信息管理文件大小,布局。
- 行和列数据的异步压缩。
- 时间轴元数据以跟踪血统。
- 通过聚类优化数据湖布局。
设计原则
- 流式读/写:
Hudi借鉴了数据库设计的原理,从零设计,应用于大型数据集记录流的输入和输出。为此,Hudi提供了索引实现,可以将记录的键快速映射到其所在的文件位置。同样,对于流式输出数据,Hudi通过其特殊列添加并跟踪记录级的元数据,从而可以提供所有发生变更的精确增量流。 - 自管理:
Hudi注意到用户可能对数据新鲜度(写友好)与查询性能(读/查询友好)有不同的期望,它支持了三种查询类型,这些类型提供实时快照,增量流以及稍早的纯列数据。在每一步,Hudi都努力做到自我管理(例如自动优化编写程序的并行性,保持文件大小)和自我修复(例如:自动回滚失败的提交),即使这样做会稍微增加运行时成本(例如:在内存中缓存输入数据已分析工作负载)。如果没有这些内置的操作杠杆/自我管理功能,这些大型流水线的运营成本通常会翻倍。 - 日志结构化存储:
Hudi还具有 append only、云数据友好的设计,该设计实现了日志结构化存储系统的原理,可以无缝管理所有云提供商的数据。 - 键-值数据模型:
在写方面,Hudi表被建模为键值对数据集,其中每条记录都有一个唯一的记录键。此外,一个记录键还可以包括分区路径,在该路径下,可以对记录进行分区和存储。这通常有助于减少索引查询的搜索空间。
Timeline(时间轴)
Hudi的核心是维护在不同时间对表执行的所有操作的时间表,这有助于提供表的即时视图,同时还有效地支持按到达顺序进行数据检索。 Hudi及时性包含以下组件
- Instant action(即时操作):在桌子上执行的操作类型
- Instant time(即时时间):即时时间通常是一个时间戳(例如:20190117010349),该时间戳会按照操作开始时间的顺序单调增加。
- state(状态):即时的当前状态
Hudi保证在时间轴上执行的操作基于当前时间是原子性和时间轴上一致的。
所执行的关键动作包括
- COMMITS - 一次提交表示将一批记录原子写入表中。
- CLEANS - 后台活动,它删除了表中不再需要的文件的较旧版本。
- DELTA_COMMIT - 增量提交是指将一批记录原子写入MergeOnRead类型表中,其中一些/所有数据都可以只写到增量日志中。
- COMPACTION 压缩 - 后台活动以协调Hudi中的差异数据结构,例如:将更新从基于行的日志文件移动到列格式。 在内部,压缩表现为时间轴上的特殊提交
- ROLLBACK - 指示提交/增量提交不成功且已回滚,删除了在此写入过程中产生的任何部分文件
- SAVEPOINT - 将某些文件组标记为“已保存”,以便清理程序不会删除它们。在发生灾难/数据恢复的情况下,它有助于将表还原到时间轴上的某个点。
任何给定的瞬间都可以处于以下状态之一
- REQUESTED - 表示已调度改动作,但尚未启动
- INFLIGHT - 表示当前正在执行该操作
- COMPLETED - 表示在时间轴上完成了一项操作
Hudi文件格式
Hudi将表组织成DFS上基本路径下的目录结构。 表分为几个分区,这些分区是包含该分区的数据文件的文件夹,与Hive表非常相似。 每个分区都由其相对于基本路径的partitionpath唯一标识。
在每个分区内,文件被组织成文件组,由文件ID唯一标识。 每个文件组包含几个文件片,其中每个片都包含在某个特定提交/压缩瞬间生成的基本文件(* .parquet),以及包含对文件进行插入/更新的一组日志文件(* .log。*)。 基本文件,因为已生成基本文件。 Hudi采用MVCC设计,其中压缩操作将日志和基本文件合并以生成新的文件片,而清理操作则将未使用的/较旧的文件片去除以回收DFS上的空间。
索引
Hudi通过使用索引机制将给定的hoodie密钥(记录密钥+分区路径)一致地映射到文件ID,从而提供了高效的Upsert。 一旦将记录的第一个版本写入文件,记录键和文件组/文件ID之间的映射就永远不会改变。 简而言之,映射文件组包含一组记录的所有版本。
表类型 & 查询方式
Hudi表类型定义了如何在DFS上对数据进行索引和布局,以及如何在此类组织之上实现上述原语和时间轴活动(即,如何写入数据)。 反过来,查询类型定义了如何将基础数据公开给查询(即,如何读取数据)。
表存储类型
Hudi支持以下存储类型表
- 写时复制(Copy On Write):
仅使用列式文件(parquet)存储数据。在写入/更新数据时,直接同步合并原文件,生成新版本的基文件(需要重写整个列数据文件,即使只有一个字节的新数据被提交)。此存储类型下,写入数据非常昂贵,而读取的成本没有增加,所以适合频繁读的工作负载,因为数据集的最新版本在列式文件中始终可用,以进行高效的查询。
- 读时合并(Merge On Read):
使用列式(parquet)与行式(avro)文件组合,进行数据存储。在更新记录时,更新到增量文件中(avro),然后进行异步(或同步)的compaction,创建列式文件(parquet)的新版本。此存储类型适合频繁写的工作负载,因为新记录是以appending 的模式写入增量文件中。但是在读取数据集时,需要将增量文件与旧文件进行合并,生成列式文件。
下表总结了这两种表类型之间的权衡
Trade-off | CopyOnWrite | MergeOnRead |
---|---|---|
Data Latency(数据延迟) | Higher | Lower |
Query Latency(查询延迟) | Lower | Higher |
Update cost (I/O)(更新时I/O资源) | Higher (rewrite entire parquet) | Lower (append to delta log) |
Parquet File Size(空间占用) | Smaller (high update(I/0) cost) | Larger (low update cost) |
Write Amplification(写时资源消耗) | Higher | Lower (depending on compaction strategy)(取决于压缩策略) |
查询方式
Hudi支持以下查询类型
- Snapshot Queries(快照查询):
查询可查看给定提交或压缩操作中表的最新快照。 如果在读取表上进行合并,它将通过即时合并最新文件切片的基本文件和增量文件来公开近乎实时的数据(几分钟)。 对于在写入表上进行复制,它提供了现有镶木地板表的直接替代品,同时提供了加高/删除和其他写入方面的功能。 - Incremental Queries(增量查询):
由于给定的提交/压缩,查询只能看到写入表的新数据。 这有效地提供了更改流以启用增量数据管道。 - Read Optimized Queries(读优化查询):
查询可以查看表中给定提交/压缩操作的最新快照。 与非Hudi柱状表相比,仅公开最新文件片中的基本/列文件,并保证相同的柱状查询性能。
下表总结了不同查询类型之间的权衡。
表类型 | 支持的查询方式 |
---|---|
Copy On Write(写时复制) | Snapshot Queries(快照查询) + Incremental Queries(增量查询) |
Merge On Read(读时合并) | Snapshot Queries(快照查询) + Incremental Queries(增量查询) + Read Optimized Queries(读优化查询) |
样例开发
使用spark-shell可以进行快速的上手操作,详细用法请参考官方给出的 入门指南