十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
今天给大家介绍一下怎么实现Apache Hudi和Presto的原理分析。文章的内容小编觉得不错,现在给大家分享一下,觉得有需要的朋友可以了解一下,希望对大家有所帮助,下面跟着小编的思路一起来阅读吧。
成都创新互联公司专注于成都网站制作、做网站、网页设计、网站制作、网站开发。公司秉持“客户至上,用心服务”的宗旨,从客户的利益和观点出发,让客户在网络营销中找到自己的驻足之地。尊重和关怀每一位客户,用严谨的态度对待客户,用专业的服务创造价值,成为客户值得信赖的朋友,为客户解除后顾之忧。
Apache Hudi 是一个快速迭代的数据湖存储系统,可以帮助企业构建和管理PB级数据湖,Hudi通过引入upserts
、deletes
和增量查询等原语将流式能力带入了批处理。这些特性使得统一服务层可提供更快、更新鲜的数据。Hudi表可存储在Hadoop兼容的分布式文件系统或者云上对象存储中,并且很好的集成了 Presto, Apache Hive, Apache Spark 和Apache Impala。Hudi开创了一种新的模型(数据组织形式),该模型将文件写入到一个更受管理的存储层,该存储层可以与主流查询引擎进行互操作,同时在项目演变方面有了一些有趣的经验。
本博客讨论Presto和Hudi集成的演变,同时讨论Presto-Hudi查询即将到来的文件Listing和查询计划优化。
Apache Hudi(简称Hudi)提供在DFS上存储超大规模数据集,同时使得流式处理如果批处理一样,该实现主要是通过如下两个原语实现。
上图说明了Hudi的原语,配合这些原语可以直接在DFS抽象之上解锁流/增量处理功能。这和直接从Kafka Topic消费事件,然后使用状态存储来增量计算临时结果类似,该架构有很多优点。
Hudi支持如下两种类型表
Copy On Write (COW): 使用列式存储格式(如parquet)存储数据,在写入时同步更新版本/重写数据。
Merge On Read (MOR): 使用列式存储格式(如parquet)+ 行存(如Avro)存储数据。更新被增量写入delta文件,后续会进行同步/异步压缩产生新的列式文件版本。
下表总结了两种表类型的trade-off。
Trade-off | CopyOnWrite | MergeOnRead |
---|---|---|
数据延迟 | 更高 | 更低 |
更新开销 (I/O) | 高(重写整个parquet文件) | 更低 (写入增量日志文件) |
Parquet文件大小 | 更小(高update (I/0) 开销) | 更大 (低updaet开销) |
写放大 | 更低 (决定与Compaction策略) |
Hudi支持如下查询类型
快照查询: 查询给定commit/compaction的表的最新快照。对于Merge-On-Read表,通过合并基础文件和增量文件来提供近实时数据(分钟级);对于Copy-On-Write表,对现有Parquet表提供了一个可插拔替换,同时提供了upsert/delete和其他特性。
增量查询: 查询给定commit/compaction之后新写入的数据,可为增量管道提供变更流。
读优化查询: 查询给定commit/compaction的表的最新快照。只提供最新版本的基础/列式数据文件,并可保证与非Hudi表相同的列式查询性能。
下表总结了不同查询类型之间的trade-off。
Trade-off | 快照 | 读优化 |
---|---|---|
数据延迟 | 更低 | 更高 |
查询延迟 | COW: 与parquet表相同。MOR: 更高 (合并基础/列式文件和行存增量文件) | 与COW快照查询有相同列式查询性能 |
下面动画简单演示了插入/更新如何存储在COW和MOR表中的步骤,以及沿着时间轴的查询结果。其中X轴表示每个查询类型的时间轴和查询结果。
注意,作为写操作的一部分,表的commit被完全合并到表中。对于更新,包含该记录的文件将使用所有已更改记录的新值重新写入。对于插入,优先会将记录写入到每个分区路径中最小文件,直到它达到配置的最大大小。其他剩余的记录都将写入新的文件id组中,会保证再次满足大小要求。
MOR和COW在摄取数据方面经历了相同步骤。更新将写入属于最新文件版本的最新日志(delta)文件,而不进行合并。对于插入,Hudi支持2种模式:
增量日志文件后面通过时间轴中的压缩(compaction)操作与基础parquet文件合并。这种表类型是最通用、高度高级的,为写入提供很大灵活性(指定不同的压缩策略、处理突发性写入流量等)和查询提供灵活性(例如权衡数据新鲜度和查询性能)。
Hudi设计于2016年中后期。那时我们就着手与Hadoop生态系统中的查询引擎集成。为了在Presto中实现这一点,正如社区建议的那样,我们引入了一个自定义注解@UseFileSplitsFromInputFormat
。任何注册的Hive表(如果有此注解)都将通过调用相应的inputformat的getSplits()
方法(而不是Presto Hive原生切片加载逻辑)来获取切片。通过Presto查询的Hudi表,只需简单调用HoodieParquetInputFormat.getSplits()
. 集成非常简单只,需将相应的Hudi jar包放到
目录下。它支持查询COW Hudi表,并读取MOR Hudi表的优化查询(只从压缩的基本parquet文件中获取数据)。在Uber,这种简单的集成已经支持每天超过100000次的Presto查询,这些查询来自使用Hudi管理的HDFS中的100PB的数据(原始数据和模型表)。
调用inputformat.getSplits()
是个简单的集成,但是可能会导致对NameNode的大量RPC调用,以前的集成方法有几个缺点。
loadPartition()
调用),
HoodieParquetInputFormat.getSplits()
将被调用。这导致了冗余的Hudi表元数据Listing,其实可以被属于从查询扫描的表的所有分区复用。我们开始重新思考Presto-Hudi的整合方案。在Uber,我们通过在Hudi上添加一个编译时依赖项来改变这个实现,并在BackgroundHiveSplitLoader
构造函数中实例化HoodieTableMetadata
一次。然后我们利用Hudi Api过滤分区文件,而不是调用HoodieParquetInputFormat.getSplits()
,这大大减少了该路径中NameNode调用次数。
为了推广这种方法并使其可用于Presto-Hudi社区,我们在Presto的DirectoryLister
接口中添加了一个新的API,它将接受PathFilter
对象。对于Hudi表,我们提供了这个PathFilter对象HoodieROTablePathFilter
,它将负责过滤为查询Hudi表而预先列出的文件,并获得与Uber内部解决方案相同的结果。
这一变化是从0.233版本的Presto开始提供,依赖Hudi版本为0.5.1-incubating。由于Hudi现在是一个编译时依赖项,因此不再需要在plugin目录中提供Hudi jar文件。
我们看到社区有越来越多人对使用Presto支持Hudi MOR表的快照查询感兴趣。之前Presto只支持查询Hudi表读优化查询(纯列式数据)。随着该PR https://github.com/prestodb/presto/pull/14795被合入,现在Presto(0.240及后面版本)已经支持查询MOR表的快照查询,这将通过在读取时合并基本文件(parquet数据)和日志文件(avro数据)使更新鲜的数据可用于查询。
在Hive中,这可以通过引入一个单独的InputFormat
类来实现,该类提供了处理切片的方法,并引入了一个新的RecordReader
类,该类可以扫描切片以获取记录。对于使用Hive查询MOR Hudi表,在Hudi中已经有类似类可用:
InputFormat
-
org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat
InputSplit
-
org.apache.hudi.hadoop.realtime.HoodieRealtimeFileSplit
RecordReader
-
org.apache.hudi.hadoop.realtime.HoodieRealtimeRecordReader
在Presto中支持这一点需要理解Presto如何从Hive表中获取记录,并在该层中进行必要的修改。因为Presto使用其原生的
ParquetPageSource
而不是InputFormat的记录读取器,Presto将只显示基本Parquet文件,而不显示来自Hudi日志文件的实时更新,后者是avro数据(本质上与普通的读优化Hudi查询相同)。为了让Hudi实时查询正常工作,我们确定并进行了以下必要更改:
向可序列化HiveSplit添加额外的元数据字段以存储Hudi切片信息。Presto-Hive将其拆分转换为可序列化的HiveSplit以进行传递。因为它需要标准的切片,所以它将丢失从FileSplit扩展的复杂切片中包含的任何额外信息的上下文。我们的第一个想法是简单地添加整个切片作为HiveSplit
的一个额外的字段。但这并不起作用,因为复杂的切片不可序列化,而且还会复制基本切片数据。
相反我们添加了一个CustomSplitConverter
接口。它接受一个自定义切片并返回一个易于序列化的String->String Map,其中包含来自自定义切片的额外数据。为了实现这点,我们还将此Map作为一个附加字段添加到Presto的HiveSplit中。我们创建了HudiRealtimeSplitConverter
来实现用于Hudi实时查询的CustomSplitConverter
接口。
从HiveSplit的额外元数据重新创建Hudi切片。现在我们已经掌握了HiveSplit中包含的自定义切片的完整信息,我们需要在读取切片之前识别并重新创建HoodieRealtimeFileSplit
。CustomSplitConverter
接口还有另一个方法,它接受普通的FileSplit和额外的split信息映射,并返回实际复杂的FileSplit,在本例中是HudiRealtimeFileSplit
。
使用HoodieParquetRealtimeInputFormat
中的HoodieRealtimeRecordReader
读取重新创建的HoodieRealtimeFileSplit
。Presto需要使用新的记录读取器来正确处理HudiRealtimeFileSplit
中的额外信息。为此,我们引入了与第一个注释类似的另一个注解@UseRecordReaderFromInputFormat
。这指示Presto使用Hive记录光标(使用InputFormat
的记录读取器)而不是PageSource
。Hive记录光标可以理解重新创建的自定义切片,并基于自定义切片设置其他信息/配置。
有了这些变更,Presto用户便可查询Hudi MOR表中更新鲜的数据了。
下面是一些很有意思的工作(RFCs),可能也需要在Presto中支持。
RFC-12: Bootstrapping Hudi tables efficiently
ApacheHudi维护每个记录的元数据,使我们能够提供记录级别的更新、唯一的键语义和类似数据库的更改流。然而这意味着,要利用Hudi的upsert和增量处理能力,用户需要重写整个数据集,使其成为Hudi表。这个RFC提供了一种机制来高效地迁移他们的数据集,而不需要重写整个数据集,同时还提供了Hudi的全部功能。
这将通过在新的引导Hudi表中引用外部数据文件(来自源表)的机制来实现。由于数据可能驻留在外部位置(引导数据)或Hudi表的basepath(最近的数据)下,FileSplits将需要在这些位置上存储更多的元数据。这项工作还将利用并建立在我们当前添加的Presto MOR查询支持之上。
支持Hudi表增量和时间点时间旅行查询
增量查询允许我们从源Hudi表中提取变更日志。时间点查询允许在时间T1和T2之间获取Hudi表的状态。这些已经在Hive和Spark中得到支持。我们也在考虑在Presto中支持这个特性。
在Hive中,通过在JobConf
中设置一些配置来支持增量查询,例如-query mode设置为INCREMENTAL
、启动提交时间和要使用的最大提交数。在Spark中有一个特定的实现来支持增量查询—IncrementalRelation
。为了在Presto中支持这一点,我们需要一种识别增量查询的方法。如果Presto不向hadoop Configuration对象传递会话配置,那么最初的想法是在metastore中将同一个表注册为增量表。然后使用查询谓词获取其他详细信息,如开始提交时间、最大提交时间等。
RFC-15: 查询计划和Listing优化
Hudi write client和Hudi查询需要对文件系统执行listStatus
操作以获得文件系统的当前视图。在Uber,HDFS基础设施为Listing做了大量优化,但对于包含数千个分区的大型数据集以及每个分区在云/对象存储上有数千个文件的大型数据集来说,这可能是一个昂贵的操作。上面的RFC工作旨在消除Listing操作,提供更好的查询性能和更快的查找,只需将Hudi的时间轴元数据逐渐压缩到表状态的快照中。
该方案旨在解决:
为此,Presto也需要一些变更。我们正在积极探索在查询规划阶段利用这些元数据的方法。这将是对Presto-Hudi集成的重要补充,并将进一步降低查询延迟。
记录级别索引
Upsert是Hudi表上一种流行的写操作,它依赖于索引将传入记录标记为Upsert。HoodieIndex
在分区或非分区数据集中提供记录id到文件id的映射,实现有BloomFilters/Key ranges(用于临时数据)和Apache HBase(用于随机更新)支持。许多用户发现Apache HBase(或任何类似的key-value-store-backed索引)很昂贵,并且增加了运维开销。该工作试图提出一种新的索引格式,用于记录级别的索引,这是在Hudi中实现的。Hudi将存储和维护记录级索引(有HFile、RocksDB等可插拔存储实现支持)。这将被writer(摄取)和reader(摄取/查询)使用,并将显著提高upsert性能,而不是基于join的方法,或者是用于支持随机更新工作负载的布隆索引。这是查询引擎在列出文件之前修剪文件时可以利用这些信息的另一个领域。我们也在考虑一种在查询时利用Presto中的元数据的方法。
以上就是怎么实现Apache Hudi和Presto的原理分析的全部内容了,更多与怎么实现Apache Hudi和Presto的原理分析相关的内容可以搜索创新互联之前的文章或者浏览下面的文章进行学习哈!相信小编会给大家增添更多知识,希望大家能够支持一下创新互联!