1. Hudi简介

Hudi表示Hadoop Upserts Deletes and Incrementals,用于管理HDFS上的大型分析数据集存储。 Hudi的主要目的是高效的减少入库延时。 Hudi是Uber开发的一个开源项目。

2. 存储类型

Hudi支持以下存储类型。

  • 写时复制 : 仅使用列文件格式(例如Parquet)存储数据。通过在写入过程中执行同步合并以更新版本并重写文件。
  • 读时合并 : 使用列式(例如Parquet)+ 基于行(例如Avro)的文件格式组合来存储数据。 更新记录到增量文件中,然后进行同步或异步压缩以生成列文件的新版本。

下表总结了这两种存储类型之间的权衡

权衡 写时复制 读时合并
数据延迟 更高 更低
更新代价(I/O) 更高(重写整个parquet文件) 更低(追加到增量日志)
Parquet文件大小 更小(高更新代价(I/o)) 更大(低更新代价)
写放大 更高 更低(取决于压缩策略)

2.1 写时复制存储

写时复制存储中的文件片仅包含基本/列文件,并且每次提交都会生成新版本的基本文件。
换句话说,我们压缩每个提交,从而所有的数据都是以列数据的形式储存。在这种情况下,写入数据非常昂贵(我们需要重写整个列数据文件,即使只有一个字节的新数据被提交),而读取数据的成本则没有增加。
这种视图有利于读取繁重的分析工作。

以下内容说明了将数据写入写时复制存储并在其上运行两个查询时,它是如何工作的。

1875937-20191210095243405-1893468574.png

随着数据的写入,对现有文件组的更新将为该文件组生成一个带有提交即时时间标记的新切片,而插入分配一个新文件组并写入该文件组的第一个切片。
这些文件切片及其提交即时时间在上面用颜色编码。
针对这样的数据集运行SQL查询(例如:select count(*)统计该分区中的记录数目),首先检查时间轴上的最新提交并过滤每个文件组中除最新文件片以外的所有文件片。
旧查询不会看到以粉红色标记的当前进行中的提交的文件,但是在该提交后的新查询会获取新数据。因此,查询不受任何写入失败/部分写入的影响,仅运行在已提交数据上。

写时复制存储的目的是从根本上改善当前管理数据集的方式,通过以下方法来实现

  • 优先支持在文件级原子更新数据,而无需重写整个表/分区
  • 能够只读取更新的部分,而不是进行低效的扫描或搜索
  • 严格控制文件大小来保持出色的查询性能(小的文件会严重损害查询性能)。

2.2 读时合并存储

读时合并存储是写时复制的升级版,从某种意义上说,它仍然可以通过读优化表提供数据集的读取优化视图(写时复制的功能)。
此外,它将每个文件组的更新插入存储到基于行的增量日志中,通过文件id,将增量日志和最新版本的基本文件进行合并,从而提供近实时的数据查询。因此,此存储类型智能地平衡了读和写的成本,以提供近乎实时的查询。
这里最重要的一点是压缩器,它现在可以仔细挑选需要压缩到其列式基础文件中的增量日志(根据增量日志的文件大小),以保持查询性能(较大的增量日志将会提升近实时的查询时间,并同时需要更长的合并时间)。

以下内容说明了存储的工作方式,并显示了对近实时表和读优化表的查询。

1875937-20191210095325312-1303673636.png

此示例中发生了很多有趣的事情,这些带出了该方法的微妙之处。

  • 现在,我们每1分钟左右就有一次提交,这是其他存储类型无法做到的。
  • 现在,在每个文件id组中,都有一个增量日志,其中包含对基础列文件中记录的更新。
    在示例中,增量日志包含10:05至10:10的所有数据。与以前一样,基本列式文件仍使用提交进行版本控制。
    因此,如果只看一眼基本文件,那么存储布局看起来就像是写时复制表的副本。
  • 定期压缩过程会从增量日志中合并这些更改,并生成基础文件的新版本,就像示例中10:05发生的情况一样。
  • 有两种查询同一存储的方式:读优化(RO)表和近实时(RT)表,具体取决于我们选择查询性能还是数据新鲜度。
  • 对于RO表来说,提交数据在何时可用于查询将有些许不同。 请注意,以10:10运行的(在RO表上的)此类查询将不会看到10:05之后的数据,而在RT表上的查询总会看到最新的数据。
  • 何时触发压缩以及压缩什么是解决这些难题的关键。
    通过实施压缩策略,在该策略中,与较旧的分区相比,我们会积极地压缩最新的分区,从而确保RO表能够以一致的方式看到几分钟内发布的数据。

读时合并存储上的目的是直接在DFS上启用近实时处理,而不是将数据复制到专用系统,后者可能无法处理大数据量。
该存储还有一些其他方面的好处,例如通过避免数据的同步合并来减少写放大,即批量数据中每1字节数据需要的写入数据量。

3. 视图

Hudi支持以下存储数据的视图

  • 读优化视图(Read Optimized View) : 在此视图上的查询将查看给定提交或压缩操作中数据集的最新快照。
    该视图仅将最新文件切片中的基本/列文件暴露给查询,并保证与非Hudi列式数据集相比,具有相同的列式查询性能。
  • 增量视图(Incremental View) : 对该视图的查询只能看到从某个提交/压缩后写入数据集的新数据。该视图有效地提供了更改流,来支持增量数据管道。
  • 实时视图(Real-time View) : 在此视图上的查询将查看某个增量提交操作中数据集的最新快照。该视图通过动态合并最新的基本文件(例如Parquet)和增量文件(例如Avro)来提供近实时数据集(几分钟的延迟)。

在以上3种视图中,"读优化视图"与"增量视图"均可在"写时复制"与"读时合并"的存储类型下使用。而"实时视图"仅能在"读时合并"模式下使用。

存储类型 支持的视图
写时复制 读优化 + 增量
读时合并 读优化 + 增量 + 近实时

下表总结了不同视图之间的权衡。

权衡 读优化 实时
数据延迟 更高 更低
查询延迟 更低(原始列式性能) 更高(合并列式 + 基于行的增量)

4. Hudi表数据结构

Hudi表的数据文件,可以使用操作系统的文件系统存储,也可以使用HDFS这种分布式的文件系统存储。为了后续分析性能和数据的可靠性,一般使用HDFS进行存储。以HDFS存储来看,一个Hudi表的存储文件分为两类。

bVcMsKg.png

  • 包含_partition_key相关的路径是实际的数据文件,按分区存储,当然分区的路径key是可以指定的,我这里使用的是_partition_key
  • .hoodie 由于CRUD的零散性,每一次的操作都会生成一个文件,这些小文件越来越多后,会严重影响HDFS的性能,Hudi设计了一套文件合并机制。 .hoodie文件夹中存放了对应的文件合并操作相关的日志文件。

4.1 数据文件

Hudi真实的数据文件使用Parquet文件格式存储

bVcMsKh.png

4.2 .hoodie文件

Hudi把随着时间流逝,对表的一系列CRUD操作叫做Timeline。Timeline中某一次的操作,叫做Instant。Instant包含以下信息

  • Instant Action 记录本次操作是一次数据提交(COMMITS),还是文件合并(COMPACTION),或者是文件清理(CLEANS)
  • Instant Time 本次操作发生的时间
  • state 操作的状态,发起(REQUESTED),进行中(INFLIGHT),还是已完成(COMPLETED)

.hoodie文件夹中存放对应操作的状态记录

bVcMsKi.png

5. 时间轴

Hudi 会维护一个时间轴,在每次执行操作时(如写入、删除、合并等),均会带有一个时间戳。通过时间轴,可以实现在仅查询某个时间点之后成功提交的数据,或是仅查询某个时间点之前的数据。这样可以避免扫描更大的时间范围,并非常高效地只消费更改过的文件。