天天看点

Hudi学习二:Hudi基本概念

文章目录

  • ​​1.时间轴 (Timeline)​​
  • ​​2.文件的布局 (File Layout)​​
  • ​​2.1 文件的存储方式​​
  • ​​2.2 文件的管理​​
  • ​​3.索引 (Index)​​
  • ​​3.1 索引选项​​
  • ​​3.2 全局索引与非全局索引​​
  • ​​3.3 索引的选择策略​​
  • ​​4.表类型(Table Types)​​
  • ​​4.1 Copy On Write​​
  • ​​4.2 Merge On Read​​
  • ​​5.查询类型(Query Types)​​

1.时间轴 (Timeline)

Hudi学习二:Hudi基本概念

Hudi的核心是维护不同时刻在表上执行的所有操作的时间表, 提供表的即时视图, 同时还有效的支持按照时间顺序检索数据, Hudi的时刻组成:

  1. Instant action: 在表上执行的操作类型
  2. Instant time: 即时时间,通常是一个时间戳,它安装action的开始时间单调递增
  3. State: 时刻的当前状态

hudi保证在时间线上的操作都是基于即时时间的,两者的时间保持一致并且是原子性的。

action:

  1. commits: 表示将一批数据原子写入表中
  2. cleans: 清除表中不在需要的旧版本文件的后台活动。
  3. delta_commit:增量提交是指将一批数据原子性写入MergeOnRead类型的表中,其中部分或者所有数据可以写入增量日志中。
  4. compaction: 协调hudi中差异数据结构的后台活动,例如:将更新从基于行的日志文件变成列格式。在内部,压缩的表现为时间轴上的特殊提交。
  5. rollback:表示提交操作不成功且已经回滚,会删除在写入过程中产生的数据
  6. savepoint:将某些文件标记为“已保存”,以便清理程序时不会被清楚。在需要数据恢复的情况下,有助于将数据集还原到时间轴上某个点。

state:

  1. requested:表示一个动作已被安排,但尚未启动
  2. inflight:表是当前正在执行操作
  3. completed:表是在时间线上完成了操作
Hudi学习二:Hudi基本概念

上图采用时间 小时 作为分区字段, 10:00 开始产生各种commits, 10:20 来了一条 9:00 的数据, 根据 event time 该数据被分到 9:00 的分区, 通过timeline直接消费10:00 之后的增量更新, 这条延迟的数据仍然可以消费到。

2.文件的布局 (File Layout)

2.1 文件的存储方式

Hudi学习二:Hudi基本概念

Hudi 的 存储分为两个部分:

  • 元数据: .hoodie 目录对应的是元数据, 包括表的版本管理 timeline, 归档目录(存放过时的instant也就是版本),一个instant记录了一次提交(commit)的行为、时间戳和状态,Hudi以时间轴的形式维护了在数据集上执行的所有操作的元数据;
  • 数据:和hive一样,以分区方式存放数据;分区里面存放着Base File(.parquet)和Log File(.log.*)

.hoodie 元数据 和 分区

Hudi学习二:Hudi基本概念

.hoodie

Hudi学习二:Hudi基本概念

存有deltacommit 增强提交的信息, 和instance的信息, 有time, state, action信息

Hudi学习二:Hudi基本概念

分区中的文件为 .log 和 .parquet 和 .metadata 文件

,metadata 文件

Hudi学习二:Hudi基本概念

.log数据文件

Hudi学习二:Hudi基本概念

2.2 文件的管理

Hudi学习二:Hudi基本概念
  1. Hudi将数据表组织成分布式文件系统基本路径(basepath)下的目录结构
  2. 表被划分为多个分区,这些分区是包含该分区的数据文件的文件夹,非常类似于Hive表
  3. 在每个分区中,文件被组织成文件组,由文件ID唯一标识
  4. 每个文件组包含几个文件片(FileSlice)
  5. 每个文件片包含:

    一个基本文件(.parquet):在某个commit/compaction即时时间(instant time)生成的(MOR可能没有)

    多个日志文件(.log.*),这些日志文件包含自生成基本文件以来对基本文件的插入/更新(COW没有)

  6. Hudi采用了多版本并发控制(Multiversion Concurrency Control, MVCC)

    其中压缩操作会将日志和基本文件合并成新的文件片,清理操作会将未使用/较旧的文件片删除来回收DFS上的空间。

    compaction操作:合并日志和基本文件以产生新的文件片

    clean操作:清除不使用的/旧的文件片以回收文件系统上的空间

  7. Hudi的base file(parquet 文件)在 footer 的 meta 去记录了 record key 组成的 BloomFilter,用于在 file based index 的实现中实现高效率的 key contains 检测。只有不在 BloomFilter 的 key 才需要扫描整个文件消灭假阳。
  8. Hudi 的 log (avro 文件)是自己编码的,通过积攒数据 buffer 以 LogBlock 为单位写出,每个 LogBlock 包含 magic number、size、content、footer 等信息,用于数据读、校验和过滤。
Hudi学习二:Hudi基本概念

3.索引 (Index)

Hudi通过索引机制将映射的给定的hoodie key(record key+partition path)映射到文件id(唯一标示),从而提供高效的upsert操作。记录键和文件组/文件ID之间的这种映射,一旦记录的第一个版本写入文件就永远不会改变。

所以,一个 FileGroup 包含了一批 record 的所有版本记录。Index 用于区分消息是 INSERT 还是 UPDATE。

Hudi学习二:Hudi基本概念

有了索引机制,可以做到:避免读取不需要的文件、避免更新不必要的文件、无需将更新数据与历史数据做分布式关联,只需要在 File Group 内做合并。

3.1 索引选项

Hudi学习二:Hudi基本概念

Flink只有一种state based index(和bucket_index),其他index是Spark可选配置。

3.2 全局索引与非全局索引

  • 全局索引:全局索引在全表的所有分区范围下强制要求键的唯一性,也就是确保对给定的键有且只有一个对应的记录。全局索引提供了更强的保证,但是随着表增大,update/delete 操作损失的性能越高,因此更适用于小表。
  • 非全局索引:默认的索引实现,只能保证数据在分区的唯一性。非全局索引依靠写入器为同一个记录的update/delete提供一致的分区路径,同时大幅提高了效率,更适用于大表。
  • 从index的维护成本和写入性能的角度考虑,维护一个global index的难度更大,对写入性能的影响也更大,所以需要non-global index。

HBase索引本质上是一个全局索引,bloom和simple index都有全局选项:

  • hoodie.index.type=GLOBAL_BLOOM
  • hoodie.index.type=GLOBAL_SIMPLE

3.3 索引的选择策略

对事实表的延迟更新

许多公司会在NoSQL数据存储中存放大量的交易数据。例如共享出行的行程表、股票买卖记录的表、和电商的订单表。这些表通常一直在增长,且大部分的更新随机发生在较新的记录上,而对旧记录有着长尾分布型的更新。这通常是源于交易关闭或者数据更正的延迟性。换句话说,大部分更新会发生在最新的几个分区上而小部分会在旧的分区。

Hudi学习二:Hudi基本概念

对于这样的作业模式,​

​布隆索引​

​​就能表现地很好,因为查询索引可以靠设置得当的布隆过滤器来​

​裁剪很多数据文件​

​。另外,如果生成的键可以以某种顺序排列,参与比较的文件数会进一步通过范围裁剪而减少。Hudi用所有文件的键域来构造区间树,这样能来高效地依据输入的更删记录的键域来排除不匹配的文件。

为了高效地把记录键和布隆过滤器进行比对,即尽量减少过滤器的读取和均衡执行器间的工作量,Hudi缓存了输入记录并使用了自定义分区器和统计规律来解决数据的偏斜。有时,如果布隆过滤器的假阳性率过高,查询会增加数据的打乱操作。Hudi支持动态布隆过滤器(设置hoodie.bloom.index.filter.type=DYNAMIC_V0)。它可以根据文件里存放的记录数量来调整大小从而达到设定的假阳性率。

对事件表的去重

事件流无处不在。从Apache Kafka或其他类似的消息总线发出的事件数通常是事实表大小的10-100倍。事件通常把时间(到达时间、处理时间)作为首类处理对象,比如物联网的事件流、点击流数据、广告曝光数等等。由于这些大部分都是仅追加的数据,插入和更新只存在于最新的几个分区中。由于重复事件可能发生在整个数据管道的任一节点,在存放到数据湖前去重是一个常见的需求。

Hudi学习二:Hudi基本概念

总的来说,低消耗去重是一个非常有挑战的工作。虽然可以用一个键值存储来实现去重(即​

​HBase索引​

​),但索引存储的消耗会随着事件数增长而线性增长以至于变得不可行。事实上,有范围裁剪功能的布隆索引是最佳的解决方案。我们可以利用作为首类处理对象的时间来构造由事件时间戳和事件id(event_ts+event_id)组成的键,这样插入的记录就有了单调增长的键。这会在最新的几个分区里大幅提高裁剪文件的效益。

对维度表的随机更删

Hudi学习二:Hudi基本概念

正如之前提到的,如果范围比较不能裁剪许多文件的话,那么布隆索引并不能带来很好的效益。在这样一个随机写入的作业场景下,更新操作通常会触及表里大多数文件从而导致布隆过滤器依据输入的更新对所有文件标明阳性。最终会导致,即使采用了范围比较,也还是检查了所有文件。使用简单索引对此场景更合适,因为它不采用提前的裁剪操作,而是直接和所有文件的所需字段连接。如果额外的运维成本可以接受的话,也可以采用HBase索引,其对这些表能提供更加优越的查询效率。

当使用全局索引时,也可以考虑通过设置hoodie.bloom.index.update.partition.path=true或hoodie.simple.index.update.partition.path=true来处理 的情况;例如对于以所在城市分区的用户表,会有用户迁至另一座城市的情况。这些表也非常适合采用Merge-On-Read表型。

4.表类型(Table Types)

Hudi学习二:Hudi基本概念
  1. Copy on Write:使用列式存储来存储数据(例如:parquet),通过在写入期间执行同步合并来简单地更新和重现文件, 在COW表中,只有数据文件/基本文件(.parquet),没有增量日志文件(.log.*)。
  2. Merge on Read:使用列式存储(parquet)+行式文件(arvo)组合存储数据。更新记录到增量文件中,然后进行同步或异步压缩来生成新版本的列式文件。

    MOR表中,包含列存的基本文件(.parquet)和行存的增量日志文件(基于行的avro格式,.log.*)。

Hudi学习二:Hudi基本概念

4.1 Copy On Write

每一个新批次写入都将创建相应数据文件的新版本(新的FileSlice),新版本文件包括旧版本文件的记录以及来自传入批次的记录(全量最新)

3 个文件组,其中包含如下数据文件

Hudi学习二:Hudi基本概念
Hudi学习二:Hudi基本概念

由于在写入期间进行合并,COW 会产生一些写入延迟。但是COW 的优势在于它的简单性,不需要其他表服务(如压缩),也相对容易调试。

4.2 Merge On Read

Merge on Read表是copy on write的超集,它仍然支持通过仅向用户公开最新的文件切片中的基本/列来对表进行查询优化。用户每次对表文件的upsert操作都会以增量日志的形式进行存储,增量日志会对应每个文件最新的ID来帮助用户完成快照查询。因此这种表类型,能够智能平衡读取和写放大(wa),提供近乎实时的数据。这种表最重要的是压缩器,它用来选择将对应增量日志数据压缩到表的基本文件中,来保持查询时的性能(较大的增量日志文件会影响合并时间和查询时间)

顾名思义,MOR表的合并成本在读取端。因此在写入期间我们不会合并或创建较新的数据文件版本。标记/索引完成后,对于具有要更新记录的现有数据文件,Hudi 创建增量日志文件并适当命名它们,以便它们都属于一个文件组。

Hudi学习二:Hudi基本概念

读取端将实时合并基本文件及其各自的增量日志文件。每次的读取延迟都比较高(因为查询时进行合并),所以 Hudi 使用压缩机制来将数据文件和日志文件合并在一起并创建更新版本的数据文件。

Hudi学习二:Hudi基本概念

户可以选择内联或异步模式运行压缩。Hudi也提供了不同的压缩策略供用户选择,最常用的一种是基于提交的数量。例如可以将压缩的最大增量日志配置为 4。这意味着在进行 4 次增量写入后,将对数据文件进行压缩并创建更新版本的数据文件。压缩完成后,读取端只需要读取最新的数据文件,而不必关心旧版本文件。

  • 对于 BloomFilter 这种无法对 log file 生成 index 的索引方案,对于 INSERT 消息仍然会写 base file (parquet format),只有 UPDATE 消息会 append log 文件(因为 base file 已经记录了该 UPDATE 消息的 FileGroup ID)。
  • 对于可以对 log file 生成 index 的索引方案,例如 Flink writer 中基于 state 的索引,每次写入都是 log format,并且会不断追加和 roll over。

5.查询类型(Query Types)

  1. Snapshot Queries:快照查询,在此视图上的查询将看到某个提交和压缩操作的最新快照。对于merge on read的表,它通过即时合并最新文件切片的基本文件和增量文件来展示近乎实时的数据(几分钟)。对于copy on write的表,它提供了对现有parquet表的直接替代,同时提供了upsert/delete和其他写入功能。
  2. Incremental Queries:增量查询,该视图智能看到从某个提交/压缩写入数据集的新数据。该视图有效地提供了chang stream,来支持增量视图
  3. Read Optimized Queries:读优化视图,在此视图上的查询将查看到给定提交或压缩操作中的最新快照。该视图将最新文件切片的列暴露个查询,并保证与非hudi列式数据集相比,具有相同列式查询功能。