Hudi学习02 -- Hudi核心概念

news2024/11/15 20:07:09

文章目录

      • 基本概念
        • 时间轴(Timeline)
        • 文件布局(File Layout)
        • 索引(Index)
          • 索引原理
          • 索引类型
          • 索引的选择策略
        • 表类型(Table Types)
        • 查询类型(Query Types)
        • 写操作(Write Operations)
        • 写流程

基本概念

时间轴(Timeline)

Hudi 的核心是维护表上在不同的即时时间Instants执行的所有操作的时间轴timeline. 有助于提供表的即时视图,还有效支持按照到达顺序检索数据。

一个Instant由以下三个部分组成

  1. Instant Action: 在表上执行的操作类型

    • COMMITS: 一次commit表示将一批数据原子性地写入一个表
    • CLEANS: 清除表中不再需要的旧版本文件的后台活动
    • DELTA_COMMIT: 增量提交指的是将一批数据原子性地写入一个MergeOnRead类型的表,其中部分或所有数据可以写入增量日志
    • COMPACTION: 合并hudi内部差异数据结构的后台活动,例如将更新操作从基于的log日志文件合并到式存储的数据文件。在内部,COMPACTION体现为timeline上的特殊提交
    • ROLLBACK: 表示当commit/delta_commit不成功时进行回滚,其会删除在写入过程中产生的部分文件
    • SAVEPOINT: 将某些文件组标记为已保存,以便其不会被删除。在发生灾难时需要恢复数据的情况下,它有助于将数据集还原到时间轴上的某个点
  2. Instant time: 是一个时间戳(例如 20221229140047416),按照动作开始的时间顺序单调递增

  3. State: Instant的当前状态

    • REQUESTED: 表示某个action已经被调度,但尚未执行
    • INFLIGHT: 表示action当前正在执行
    • COMPLETED: 表示timeline上的action已经完成

两个时间概念:

  • Arrival time: 数据到达hudi的时间,commit time
  • Event time: record中记录的时间

官网示例如下:
在这里插入图片描述
上图中采用时间(小时)作为分区字段,从10:00开始陆续产生各种commits, 10:20来了一条09:00的数据(这就是延迟数据),根据event time该数据仍然可以落到09:00对应的分区,通过timeline直接消费10:00之后的增量更新(即10点之后的commits),那么这条延迟的数据仍然可以被消费到的。

文件布局(File Layout)

hudi 将一个表映射为如下结构
在这里插入图片描述
一个hudi表分为如下两部分

  1. 元数据: .hoodie目录对应着表的元数据信息,包括表的版本管理(timeline)、归档目录(archived,存放过时的Instant)。一个Instant记录了一个commit的行为、时间戳和状态。Hudi以时间轴的形式维护了在数据集上执行的所有操作的元数据
    在这里插入图片描述

  2. 数据区:和hive一样,以分区方式存放数据;分区里面存放着Base File.parquet)和Log File.log.*
    在这里插入图片描述
    在这里插入图片描述

如果是一个分区表(默认就是分区,如果用Sparksql可以创建非分区表,那么数据区下面就直接存放的是数据文件BaseFile),每个分区路径下面有多个文件组(File Group),每个文件组有唯一的文件ID(File Id)标识,每个文件组有多个文件片(File Slice),每个文件片由一个数据文件Base File(.parquet)和多个日志文件(.log.*)组成. 像cow类型的表是没有.log文件的(后面表类型再谈)
在这里插入图片描述

  • Hudi将数据表组织成分布式文件系统基本路径(basepath)下的目录结构
  • 表被划分为多个分区,这些分区是包含该分区的数据文件的文件夹,非常类似于Hive表
  • 每个分区路径下面有多个文件组(File Group),每个文件组有唯一的文件ID(File Id)标识
  • 每个文件组有多个文件片(File Slice)
  • 每个文件片由一个数据文件Base File(.parquet)和多个日志文件(.log.*)组成
    • 一个数据文件也叫一个基本文件:在某个instant通过commit或compaction生成。mor类型的表在未compaction前可能没有
    • 多个日志文件(.log.*):这些日志文件包含自生成基本文件以来对基本文件的插入更新,cow类型的表没有日志文件
  • huid采用了多版本并发控制(Multiversion Concurrency Control, MVCC
    • compaction操作:合并日志和基本文件以产生新的文件片
    • clean操作:清除不适用的或旧的文件片以回收文件系统上的空间
      在这里插入图片描述
  • Hudi的base fileparquet 文件)在 footer 的 meta 区记录了 record key 组成的 BloomFilter,用于在 file based index 的实现中实现高效率的 key contains 检测
  • Hudi 的 logavro 文件)是自己编码的,通过积攒数据 buffer 以 LogBlock 为单位写出,每个 LogBlock 包含 magic number、size、content、footer 等信息,用于数据读、校验和过滤
    在这里插入图片描述

索引(Index)

索引原理

hudi 通过索引机制提供高效的upserts,具体是将给定的hoodie key (record key + partition path) 与文件ID(文件组)建立唯一映射。这种映射关系,在数据第一次写入文件后永远保持不变,换言之,一个File Group包含了一批Record的所有版本记录。Index 用于区分是Insert还是Update

对于Copy-On-Write类型的表,这种索引机制通过避免连接整个数据集来确定哪些数据文件需要被重写,可以实现快速的upserts/delete操作。

对于Merge-On-Read类型的表,对于任何给定的基本文件只需要根据这些基本文件的record记录key的更新来进行合并即可。

相比之下,没有索引的设计如(Hive ACID),对于传入的所有更新/删除记录都不得不去和所有的基本文件进行合并。

在这里插入图片描述
上图是官网的例子,Hudi 为了消除不必要的读写,引入了索引的实现。在有了索引之后,更新的数据可以快速被定位到对应的 File Group。上图为例,白色是基本文件,黄色是更新数据,有了索引机制,可以做到:避免读取不需要的文件、避免更新不必要的文件、无需将更新数据与历史数据做分布式关联,只需要在File Group内做合并。

索引类型
Index类型原理优点缺点
Bloom Index (默认)使用布隆过滤器来判断记录存在与否,也可选使用record key的范围裁剪需要的文件效率高,不依赖外部系统,数据和索引保持一致性因假阳性问题(hash冲突)还需回溯源文件再查找以便
Simple Index把update/delete操作的新数据和老数据进行join实现最简单,无需额外的资源性能比较差
HBase Index把index存放在HBase里面。在插入 File Group定位阶段所有task向HBase发送 Batch Get 请求,获取 Record Key 的 Mapping 信息对于小批次的keys,查询效率高需要外部的系统,增加了运维压力
Flink State-based IndexHUDI 在 0.8.0 版本中实现的 Flink witer,采用了 Flink 的 state 作为底层的 index 存储,每个 records 在写入之前都会先计算目标 bucket ID不同于 BloomFilter Index,避免了每次重复的文件 index 查找

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

全局索引与非全局索引

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

从index的维护成本和写入性能的角度考虑,维护一个global index的难度更大,对写入性能的影响也更大,所以需要non-global index。

HBase索引本质上是一个全局索引,Bloom IndexSimple Index都有全局选项:

hoodie.index.type=GLOBAL_BLOOM
hoodie.index.type=GLOBAL_SIMPLE
索引的选择策略

一、对事实表的延迟更新

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

对于这样的作业模式,Bloom Index就能表现地很好,因为查询索引可以靠设置得当的布隆过滤器来裁剪很多数据文件。另外,如果生成的键可以以某种顺序排列,参与比较的文件数会进一步通过范围裁剪而减少。Hudi用所有文件的键域来构造区间树,这样能来高效地依据输入的更删记录的键域来排除不匹配的文件。

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

二、对事件表的去重

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

在这里插入图片描述

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

三、对维度表的随机更删

在这里插入图片描述

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

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

表类型(Table Types)

一、Copy-On-Write类型的表

COW表中,只有列式存储的数据文件.parquet,没有行式存储的日志文件.log.*

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

假设我们有 3 个文件组,其中包含如下数据文件。
在这里插入图片描述
我们进行一批新的写入,在索引后,我们发现这些记录与File group 1 和File group 2 匹配,然后有新的插入,我们将为其创建一个新的文件组(File group 4)。
在这里插入图片描述

因此data_file1 和 data_file2 都将创建更新的版本,data_file1 V2 是data_file1 V1 的内容与data_file1 中传入批次匹配记录的记录合并。

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



二、Merge-On-Read类型的表

MOR表包含列存的基本文件(.parquet)和行存的增量日志文件(基于行的avro格式,.log.*)。增量更新会被记录到日志文件中,然后以同步或异步的方式进行合并compaction来生成新的基本文件(.parquet)。

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

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

在这里插入图片描述

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

MOR表的写入行为,依据 index 的不同会有细微的差别:

  • 对于 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。

三、COW表和MOR表的比较

Trade-OffCOWMOR
数据延迟
查询延迟
更新IO开销高(重写所有的数据文件)低(追加到日志文件即可)
Parquet文件大小单个全量使用parquet文件存储,占用空间小单个全量存储使用parquet+avro格式存储,占用空间相对大
写放大低(依赖于合并策略)

查询类型(Query Types)

表类型支持的查询类型
COWSnapshot Queries + Incremental Queries
MORSnapshot Queries + Incremental Queries + Read Optimized Queries
  1. Snapshot Queries
    快照查询,可以查询指定commitcompaction操作后表的最新快照。
    对于MOR类型的表,它通过即时合并最新文件片的基本文件和增量文件来提供近实时的表数据(分钟级)
    对于COW类型的表,它可以替代现有的parquet表(或相同基本文件类型的表),同时提供upsert/delete和其他写入方面的功能,可以理解为查询最新版本的Parquet数据文件

    下图是官方提供的COW类型的表的快照查询示意图
    在这里插入图片描述

  2. Incremental Queries
    增量查询,可以查询给定commitcompaction操作后的新写入的数据。有效地提供变更流来启用增量数据管道。

  3. Read Optimized Queries
    读优化查询MOR类型的表独有的查询,相比于MOR表的快照查询读优化查询主要是为了保证查询效率,只提供表的基本文件的查询而不合并表的增量日志文件。

    下图是MOR表快照查询与读优化查询的对比。
    在这里插入图片描述
    Read Optimized Queries是对Merge On Read表类型快照查询的优化。

快照查询读优化查询
数据延迟
查询延迟高(合并列式的基本文件和行式的增量日志文件)低(只返回列式的基本文件)

在这里插入图片描述
在这里插入图片描述

写操作(Write Operations)

下面几种hudi的写操作都能在每次commit前去设置。知道这些写操作的区别就能知道在哪种场景选择更加合适的操作。

  1. UPSERT
    默认行为,记录records会首先通过Index打标是insert还是update,有一些启发式算法决定消息的组织以优化文件的大小。使用UPSERT操作类型写入的hudi表不会有重复数据

  2. INSERT
    类似于upsert,但是跳过了Index打标步骤,性能上比upsert要好。如果只是需要 Hudi 的事务写/增量拉取数据/存储管理的能力,并且可以容忍重复数据,那么可以选择 INSERT 操作。

  3. BULK_INSERT
    实现了基于排序的数据写入算法,对hudi表大数据量初始化很友好。该操作性能是最高的,但是无法控制小文件,而UPSERT和INSERT操作使用启发式方法可以很好的控制小文件。

在确定数据都为新数据时建议使用INSERT,当存在更新数据时建议使用UPSERT,当初始化数据集时建议使用BULK_INSERT

  1. DELETE
    hudi 对hudi表中的数据有两种类型的删除方式
    • 软删除:将除hoodie key (record key + partition path) 之外的所有字段都置为null值即可
    • 物理删除:
      • 在使用DataSource时,将OPERATION_OPT_KEY设置为DELETE_OPERATION_OPT_VAL
      • 在使用DataSource时,将PAYLOAD_CLASS_OPT_KEY设置为org.apache.hudi.EmptyHoodieRecordPayload
      • 在使用DataSourceDeltaStreamer时,给数据集添加一个_hoodie_is_deleted字段,其中要删除的数据该字段设置为true,要保留的数据该字段设置为falsenull

写流程

  1. 去重:如果同一批数据有重复的key值,则需要去重保留最新的记录
  2. 索引查询:查询该批数据是属于哪个File Group并打上InsertUpdate的标记
  3. 文件大小:hudi会基于上次commit的数据大小,尽量往小文件上添加足够多的数据以达到文件最大阈值
  4. 分区:决定该批数据属于哪一些分区或者需要新建分区
  5. 写IO:此时进行数据写入,要么生成一个新的数据文件,要么写入到日志文件中,要么对数据文件更新一个版本
  6. 更新索引:数据在写入完成后需要更新索引
  7. 提交:commit所有的变更动作
  8. 清理(若必要):commit之后如有必要可做一些清理动作
  9. 合并:如果是MOR表,compaction动作会同步或异步执行
    10.归档:执行archive动作将老版本的文件归档到指定归档目录

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/130819.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt第五十二章:Qt Design Studio使用技巧。

一、运行项目和Debugging项目【快捷键:CtrR】 二、 预览单Qml文件 三、添加资源文件 (使用资源:将资源拖动到Editor中的矩形中即可) 四、多状态【正常状态、按下状态、划过状态、已点击状态...】 注意:多状态看起来像…

java短网址平台

git地址 Reduce: 短网址平台,Coody Framework首秀,自写IOC、MVC、ORM、TASK、JSON、DB连接池、服务器。百毫秒启动,全项目仅2.1M(低配服可运行) reduce短网址平台 测试站地址:http://dev.icoody.cn/ 技…

DOM事件

鼠标事件监听 键盘事件监听 表单事件监听 常见的页面事件监听 事件传播 事件传播顺序:从内到外(冒泡阶段)onxxx这样写法只能监听冒泡阶段 addEventListener()方法第三个参数如果为true监听捕获阶段,false监听冒泡阶段(默认) 最…

C语言及算法设计课程实验二:数据类型、运算符和简单的输入输出

C语言及算法设计课程实验二:数据类型、运算符和简单的输入输出一、实验目的二、实验内容2.1、输入并运行教材第3章第4题给出的程序:2.2、输入第3章第5题的程序2.3、输入以下程序:2.4、程序设计题:假如我国国民生产总值的年增长率为…

遗传算法解决函数优化问题

遗传算法解决函数优化问题 作者: Cukor丘克环境: MatlabR2020a vscode 为什么要学习遗传算法 为什么要学习遗传算法,或者说遗传算法有什么厉害的地方。例如求解以下函数优化问题:minf(x1,x2)x12x1225∗(sin2x1sin2x2),−10≤x1≤10,−10≤x2≤10.min…

【ACWING】【图的深度优先遍历】【846树的重心】

给定一颗树,树中包含 n个结点(编号 1∼n)和 n−1条无向边。 请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。 重心定义:重心是指树中的一个结点,如果将这个点删除后&…

js复习之正则表达式正向肯定与否定预查询

正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。 正则表达式_百度百科 除开常用基本匹配模式,偶尔也会用到…

回顾艰难且不失温度的 2022 年 | 文中附「双12免单王」获奖名单

今天是 2022 年最后一天,回忆往昔,这一年经历了太多的不可思议和无可奈何之事。在年末的短短几周,寒气长驱直下以惊人的速度传给每一个人。我们真诚地希望大家都可以平安渡过这一难关。 即使步步难行,亦要踱步前行!无…

力扣刷题记录——190. 颠倒二进制位、191. 位1的个数、202. 快乐数

本专栏主要记录力扣的刷题记录,备战蓝桥杯,供复盘和优化算法使用,也希望给大家带来帮助,博主是算法小白,希望各位大佬不要见笑,今天要分享的是——《190. 颠倒二进制位、191. 位1的个数、202. 快乐数》。 目…

Gradle学习笔记之依赖

文章目录依赖的方式直接依赖项目依赖本地jar包依赖依赖的类型api和implementation的区别依赖冲突及解决方案移除某个依赖不允许依赖传递强制使用某个版本依赖冲突时立刻构建失败依赖的方式 Gradle中的依赖方式有直接依赖、项目依赖和本地jar包依赖三种: dependenc…

【一起从0开始学习人工智能0x02】字典特征抽取、文本特征抽取、中文文本特征抽取

注:最后有面试挑战,看看自己掌握了吗 文章目录什么是特征工程?用什么做?1.特征提取特征值化:特征提取API字典特征提取---向量化---类别--》one-hot编码哑变量one-hot-------直接1234会产生歧义,不公平应用场…

Python 10k+ 面试试题,看看你是否掌握

前言 大家早好、午好、晚好吖 ❤ ~ 面试实战题:采集世界最大旅游平台Tripadvisor 另我给大家准备了一些资料,包括: 2022最新Python视频教程、Python电子书10个G (涵盖基础、爬虫、数据分析、web开发、机器学习、人工智能、面试题&#xff…

Python GUI编程:音乐播放器(多线程、爬虫、进度条、文件)

文章目录1. 程序运行结果2.程序实现原理3. GUI布局4. 功能介绍5. 代码实现1. 程序运行结果 Python实现音乐播放器(爬虫、多线程、进度条、文件)2.程序实现原理 本音乐播放器GUI方面运用Python的tkinter实现,播放的音乐来自网络爬虫和本电脑已经有的。为了使整个程序…

Android studio设置全屏显示的两种方式

两种在Androidstudio中设置全屏的方式,推荐第二种 第一种(Java文件中设置) 直接在onCreate()函数中设置 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);package com.exa…

MARKETS AND MARKET LOGIC——The Market‘s Principles (6)_3

市场的组成——对行为观察的反思 制定市场理解 理解市场逻辑将有助于每个参与者提高其在市场上成功的可能性,因为他将能够阅读市场活动并接收市场生成的信息,这些信息很少有参与者承认或理解。这一信息特别涉及市场如何接受或拒绝随着时间的推移而升高或…

Linux用户标识符UID与GID和用户账号

1.用户标识符UID和GID UID 表示的是用户的标识(User Identification) GID 表示的是用户组的标识(Group Identification) 显示用户ID信息。 命令:id username 2.用户账号 用户和用户组的信息都存放在…

RabbitMQ(一)Windows下载安装

目录一、下载安装包二、安装erlang三、安装RabbitMQ四、配置RabbitMQ管理界面官网地址:https://www.rabbitmq.com/ 下载地址:https://www.rabbitmq.com/download.html 一、下载安装包 RabbitMQ Windows下载地址:https://www.rabbitmq.com/in…

001. Nginx场景,优点,组成部分和编译

目录一: Nginx三个重要的使用场景二: Nginx的优点三:Nginx文件的组成部分四:编译Nginx一: Nginx三个重要的使用场景 静态资源服务 疑问:为什么需要静态资源服务?答: 在整个的web请求…

string_string数据类型概括

目录string数据类型创建字符串的三种方式检查字符串的方法常用的字符串方法字符串遍历切割字符串为数组​截取子串判断某个字符串是否出现在当前字符串中文本匹配-match、(es6)matchAll文本替换-replace、(es6)replaceAll(es6)获取某个位置的字符串有关ASCII码的方法获取某字符…

Good Bye 2022: 2023 is NEAR C Koxia and Number Theory

Problem - C - Codeforces 题意: 给定一个数列a,问你是否存在一个数 x 使得所有数加上x之后两两互质 思路: 一、 我们知道一个结论,一些数两两之间互质,就说明所有数之间的质因子都不重合 如果要我们去判断一堆数…