总述
Hudi提供两类型表:写时复制(Copy on Write, COW)表和读时合并(Merge On Read, MOR)表。
对于Copy-On-Write Table,用户的update会重写数据所在的文件,所以是一个写放大很高,但是读放大为0,适合写少读多的场景。
对于Merge-On-Read Table,整体的结构有点像LSM-Tree,用户的写入先写入到delta data中,这部分数据使用行存,这部分delta data可以手动merge到存量文件中,整理为parquet的列存结构。
数据计算模型
hudi是Uber主导开发的开源数据湖框架,所以大部分的出发点都来源于Uber自身场景,比如司机数据和乘客数据通过订单ID来做join等。
在hudi过去的使用场景里,和大部分公司的架构类似,采用批式和流式共存的Lambda架构,后来Uber提出增量Incremental模型,相对批式来讲,更加实时,相对流式而言,更加经济。
1.批式模型(Batch)
批式模型就是使用MapReduce、Hive、Spark等典型的批计算引擎,以小时任务或者天任务的形式来做数据计算
特性
A.延迟:小时级延迟或者天级别延迟。这里的延迟不单单指的是定时任务的时间,在数据架构里,这里的延迟时间通常是定时任务间隔时间+一系列依赖任务的计算时间+数据平台最终可以展示结果的时间。数据量大、逻辑复杂的情况下,小时任务计算的数据通常真正延迟的时间是2-3小时。
B.数据完整度:数据较完整。以处理时间为例,小时级别的任务,通常计算的原始数据已经包含了小时内的所有数据,所以得到的数据相对较完整。但如果业务需求是事件时间,这里涉及到终端的一些延迟上报机制,在这里,批式计算任务就很难派上用场。
C.成本:成本很低。只有在做任务计算时,才会占用资源,如果不做任务计算,可以将这部分批式计算资源出让给在线业务使用。从另一个角度来说成本是挺高的,如原始数据做了一些增删改查,数据晚到的情况,那么批式任务是要全量重新计算。
2.流式模型(Stream)
流式模型,典型的就是使用Flink来进行实时的数据计算
特性
A.延迟:很短,甚至是实时。
B.数据完整度:较差。因为流式引擎不会等到所有数据到齐之后再开始计算,所以有一个watermark的概念,当数据的时间小于watermark时,就会被丢弃,这样是无法对数据完整度有一个绝对的保障。在互联网场景中,流式模型主要用于活动时的数据大盘展示,对数据的完整度要求并不算很高。在大部分场景中,用户需要开发两个程序,一是流式数据生产流式结果,而是批式计算人物,用于次日修复实时结果
C.成本:很高。因为流式任务时常驻的,并且对于多流join的场景,通常要借助内存或者数据库来做state的存储,不管是序列化开销,还是和外部组件交互产生的额外IO,在大数据量下都是不容忽视的。
3.增量模型(Incremental)
针对批式和流式的优缺点,Uber提出了增量模型(Incremental Mode),相对批式来讲,更加实时;相对流式而言,更加经济。
增量模型,简单来讲,就是一mini batch的形式来跑准实时任务。hudi在增量模型中支持了两个最重要的特性:
A.Upsert:这个主要是解决批式模型中,数据不能插入、更新的问题,有了这个特性,可以往Hive中写入增量数据,而不是每次进行完全的覆盖。(hudi自身维护了key-file的映射,所以当upsert时很容易找到key对应的文件)
B.Incremental Query:增量查询,减少计算的原始数据量。以uber中司机和乘客的数据流join为例,每次抓取两条数据流中的增量数据进行批式的join即可,相比流式数据而言,成本要降低几个数量级。
查询类型(Query Type)
Hudi支持三种不同的查询表的方式:Snapshot Queries(快照查询)、Incremental Queries(增量查询)和Read Optimized Queries(读优化查询).
1.Snapshot Queries(快照查询)
查询某个增量提交操作中数据集的最新快照,先进行动态合并最新的基本文件(parquet)和增量文件(Avro)来提供近实时数据集(通常会存在几分钟的延迟)
读取所有partition下每个FileGroup最新的FileSlice中的文件,Copy On Write表读parquet文件,Merge On Read表读parquet + log文件
2.Incremental Queries(增量查询)
仅查询新写入数据集的文件,需要指定一个Commit/Compaction的即时时间(位于Timeline上的某个instant)作为条件,来查询此条件之后的新数据
可查看自给定commit/delta commit即时操作依赖新写入的数据,有效地提供变更流来启用增量数据管道
3.Read Optimized Queries(读优化查询)
直接查询基本文件(数据集的最新快照),其实就是列式文件(Parquet)。并保证与非hudi列式数据集相比,具有相同的列式查询性能
可查看给定的commit/compact即时操作的表的最新快照
读优化查询和快照查询相同仅访问基本文件,提供给定文件片自上次执行压缩操作以来的数据。通常查询数据的最新程度的保证取决于压缩策略
表类型解析
1.Copy On Write
简称COW,它实在数据写入的时候,复制一份原来的拷贝,在其基础上添加新数据
正在读数据的请求,读取的是最近的完整副本,这类似于MySQL的MVCC思想
*优点:读取时,只读取对应分区的一个数据文件即可,较为高效
*缺点:数据写入的时候,需要复制一个先前的副本再在其基础上生成新的数据文件,这个过程比较耗时
COW表主要使用列式文件格式(parquet)存储数据,在写入数据过程中,执行同步合并,更新数据版本并重写数据文件,类似RDBMS中的B-Tree更新
A.更新update:在更新记录时,hudi会先找到包含更新数据的文件,然后再使用更新值(最新的数据)重写该文件,包含其他记录的文件保持不变。当突然有大量写操作时会导致重写大量文件,从而导致极大的IO开销
B.读取read:在读取数据时,通过读取最新的数据文件来获取最新的更新,此存储类型适用于少量写入和大量读取的场景
2.Merge On Read
简称MOR,新插入的数据存储在delta log中,定期再将delta log合并进行parquet数据文件
读取数据时,会将delta log跟老的数据文件做merge,得到完整的数据返回
*优点:由于写入数据先写delta log,且delta log较小,所以写入成本较低
*缺点:需要定期合并整理compact,否则碎片文件较多。读取性能较差,因为需要将delta log和老数据文件合并
MOR表是COW表的升级版,他使用列式(parquet)与行式(avro)文件混合的方式存储数据。在更新记录时,类似nosql中的LSM-Tree更新
A.更新:在更新记录时,仅更新到增量文件(avro)中,然后进行异步(或同步)的从没怕餐厅,最后创建列式文件(parquet)的新版本。次存储类型适合频繁写的工作负载,因为新纪录是以追加的模式写入增量文件中
B.读取:在读取数据集时,需要先将增量文件与旧文件进行合并,然后圣承列式文件成功后,在进行查询
COW vs MOR
对于写时复制(COW)和读时合并(MOR)writer来说,Hudi的WriteClient是相同的。
COW表:用户在snapshot读取的时候会扫描所有最新的FileSlice的base file
MOR表:在READ OPTIMIZED模式下,只会读最近的经过compaction的commit