厦门企业网站开发公司厦门建设工程招标中心的网站
web/
2026/1/13 15:31:36/
文章来源:
厦门企业网站开发公司,厦门建设工程招标中心的网站,免费咨询律师微信公众号,天河怎样优化网站建设背景
随着大数据时代的到来#xff0c;越来越多的数据流向了Hadoop生态圈#xff0c;同时对于能够快速的从TB甚至PB级别的数据中获取有价值的数据对于一个产品和公司来说更加重要#xff0c;在Hadoop生态圈的快速发展过程中#xff0c;涌现了一批开源的数据分析引擎#…背景
随着大数据时代的到来越来越多的数据流向了Hadoop生态圈同时对于能够快速的从TB甚至PB级别的数据中获取有价值的数据对于一个产品和公司来说更加重要在Hadoop生态圈的快速发展过程中涌现了一批开源的数据分析引擎例如Hive、Spark SQL、Impala、Presto等同时也产生了多个高性能的列式存储格式例如RCFile、ORC、Parquet等本文主要从实现的角度上对比分析ORC和Parquet两种典型的列存格式并对它们做了相应的对比测试。
列式存储
由于OLAP查询的特点列式存储可以提升其查询性能但是它是如何做到的呢这就要从列式存储的原理说起从图1中可以看到相对于关系数据库中通常使用的行式存储在使用列式存储时每一列的所有元素都是顺序存储的。由此特点可以给查询带来如下的优化
查询的时候不需要扫描全部的数据而只需要读取每次查询涉及的列这样可以将I/O消耗降低N倍另外可以保存每一列的统计信息(min、max、sum等)实现部分的谓词下推。由于每一列的成员都是同构的可以针对不同的数据类型使用更高效的数据压缩算法进一步减小I/O。由于每一列的成员的同构性可以使用更加适合CPU pipeline的编码方式减小CPU的缓存失效。图1 行式存储VS列式存储 嵌套数据格式
通常我们使用关系数据库存储结构化数据而关系数据库支持的数据模型都是扁平式的而遇到诸如List、Map和自定义Struct的时候就需要用户自己解析但是在大数据环境下数据的来源多种多样例如埋点数据很可能需要把程序中的某些对象内容作为输出的一部分而每一个对象都可能是嵌套的所以如果能够原生的支持这种数据查询的时候就不需要额外的解析便能获得想要的结果。例如在Twitter他们一个典型的日志对象一条记录有87个字段其中嵌套了7层如下图。 图2 嵌套数据模型 随着嵌套格式的数据的需求日益增加目前Hadoop生态圈中主流的查询引擎都支持更丰富的数据类型例如Hive、SparkSQL、Impala等都原生的支持诸如struct、map、array这样的复杂数据类型这样促使各种存储格式都需要支持嵌套数据格式。
Parquet存储格式
Apache Parquet是Hadoop生态圈中一种新型列式存储格式它可以兼容Hadoop生态圈中大多数计算框架(Mapreduce、Spark等)被多种查询引擎支持Hive、Impala、Drill等并且它是语言和平台无关的。Parquet最初是由Twitter和Cloudera合作开发完成并开源2015年5月从Apache的孵化器里毕业成为Apache顶级项目。
Parquet最初的灵感来自Google于2010年发表的Dremel论文文中介绍了一种支持嵌套结构的存储格式并且使用了列式存储的方式提升查询性能在Dremel论文中还介绍了Google如何使用这种存储格式实现并行查询的如果对此感兴趣可以参考论文和开源实现Drill。
数据模型
Parquet支持嵌套的数据模型类似于Protocol Buffers每一个数据模型的schema包含多个字段每一个字段有三个属性重复次数、数据类型和字段名重复次数可以是以下三种required(只出现1次)repeated(出现0次或多次)optional(出现0次或1次)。每一个字段的数据类型可以分成两种group(复杂类型)和primitive(基本类型)。例如Dremel中提供的Document的schema示例它的定义如下
message Document {required int64 DocId;optional group Links {repeated int64 Backward;repeated int64 Forward; }repeated group Name {repeated group Language {required string Code;optional string Country; }optional string Url; }
}1234567891011121314
可以把这个Schema转换成树状结构根节点可以理解为repeated类型如图3。 图3 Parquet的schema结构 可以看出在Schema中所有的基本类型字段都是叶子节点在这个Schema中一共存在6个叶子节点如果把这样的Schema转换成扁平式的关系模型就可以理解为该表包含六个列。Parquet中没有Map、Array这样的复杂数据结构但是可以通过repeated和group组合来实现的。由于一条记录中某一列可能出现零次或者多次需要标示出哪些列的值构成一条完整的记录。这是由Striping/Assembly算法实现的。
由于Parquet支持的数据模型比较松散可能一条记录中存在比较深的嵌套关系如果为每一条记录都维护一个类似的树状结可能会占用较大的存储空间因此Dremel论文中提出了一种高效的对于嵌套数据格式的压缩算法Striping/Assembly算法。它的原理是每一个记录中的每一个成员值有三部分组成Value、Repetition level和Definition level。value记录了该成员的原始值可以根据特定类型的压缩算法进行压缩两个level值用于记录该值在整个记录中的位置。对于repeated类型的列Repetition level值记录了当前值属于哪一条记录以及它处于该记录的什么位置对于repeated和optional类型的列可能一条记录中某一列是没有值的假设我们不记录这样的值就会导致本该属于下一条记录的值被当做当前记录的一部分从而造成数据的错误因此对于这种情况需要一个占位符标示这种情况。
通过Striping/Assembly算法parquet可以使用较少的存储空间表示复杂的嵌套格式并且通常Repetition level和Definition level都是较小的整数值可以通过RLE算法对其进行压缩进一步降低存储空间。
文件结构
Parquet文件是以二进制方式存储的是不可以直接读取和修改的Parquet文件是自解析的文件中包括该文件的数据和元数据。在HDFS文件系统和Parquet文件中存在如下几个概念
HDFS块(Block)它是HDFS上的最小的副本单位HDFS会把一个Block存储在本地的一个文件并且维护分散在不同的机器上的多个副本通常情况下一个Block的大小为256M、512M等。HDFS文件(File)一个HDFS的文件包括数据和元数据数据分散存储在多个Block中。行组(Row Group)按照行将数据物理上划分为多个单元每一个行组包含一定的行数在一个HDFS文件中至少存储一个行组Parquet读写的时候会将整个行组缓存在内存中所以如果每一个行组的大小是由内存大的小决定的。列块(Column Chunk)在一个行组中每一列保存在一个列块中行组中的所有列连续的存储在这个行组文件中。不同的列块可能使用不同的算法进行压缩。页(Page)每一个列块划分为多个页一个页是最小的编码的单位在同一个列块的不同页可能使用不同的编码方式。
通常情况下在存储Parquet数据的时候会按照HDFS的Block大小设置行组的大小由于一般情况下每一个Mapper任务处理数据的最小单位是一个Block这样可以把每一个行组由一个Mapper任务处理增大任务执行并行度。Parquet文件的格式如下图所示。 图4 Parquet文件结构 上图展示了一个Parquet文件的结构一个文件中可以存储多个行组文件的首位都是该文件的Magic Code用于校验它是否是一个Parquet文件Footer length存储了文件元数据的大小通过该值和文件长度可以计算出元数据的偏移量文件的元数据中包括每一个行组的元数据信息和当前文件的Schema信息。除了文件中每一个行组的元数据每一页的开始都会存储该页的元数据在Parquet中有三种类型的页数据页、字典页和索引页。数据页用于存储当前行组中该列的值字典页存储该列值的编码字典每一个列块中最多包含一个字典页索引页用来存储当前行组下该列的索引目前Parquet中还不支持索引页但是在后面的版本中增加。
数据访问
说到列式存储的优势Project下推是无疑最突出的它意味着在获取表中原始数据时只需要扫描查询中需要的列由于每一列的所有值都是连续存储的避免扫描整个表文件内容。
在Parquet中原生就支持Project下推执行查询的时候可以通过Configuration传递需要读取的列的信息这些列必须是Schema的子集Parquet每次会扫描一个Row Group的数据然后一次性得将该Row Group里所有需要的列的Cloumn Chunk都读取到内存中每次读取一个Row Group的数据能够大大降低随机读的次数除此之外Parquet在读取的时候会考虑列是否连续如果某些需要的列是存储位置是连续的那么一次读操作就可以把多个列的数据读取到内存。
在数据访问的过程中Parquet还可以利用每一个row group生成的统计信息进行谓词下推这部分信息包括该Column Chunk的最大值、最小值和空值个数。通过这些统计值和该列的过滤条件可以判断该Row Group是否需要扫描。另外Parquet未来还会增加诸如Bloom Filter和Index等优化数据更加有效的完成谓词下推。
ORC文件格式
ORC文件格式是一种Hadoop生态圈中的列式存储格式它的产生早在2013年初最初产生自Apache Hive用于降低Hadoop数据存储空间和加速Hive查询速度。和Parquet类似它并不是一个单纯的列式存储格式仍然是首先根据行组分割整个表在每一个行组内进行按列存储。ORC文件是自描述的它的元数据使用Protocol Buffers序列化并且文件中的数据尽可能的压缩以降低存储空间的消耗目前也被Spark SQL、Presto等查询引擎支持但是Impala对于ORC目前没有支持仍然使用Parquet作为主要的列式存储格式。2015年ORC项目被Apache项目基金会提升为Apache顶级项目。
数据模型
和Parquet不同ORC原生是不支持嵌套数据格式的而是通过对复杂数据类型特殊处理的方式实现嵌套格式的支持例如对于如下的hive表
CREATE TABLE orcStructTable(name string,course structcourse:string,score:int,score mapstring,int,work_locations arraystring)12345
ORC格式会将其转换成如下的树状结构 图5 ORC的schema结构 在ORC的结构中这个schema包含10个column其中包含了复杂类型列和原始类型的列前者包括LIST、STRUCT、MAP和UNION类型后者包括BOOLEAN、整数、浮点数、字符串类型等其中STRUCT的孩子节点包括它的成员变量可能有多个孩子节点MAP有两个孩子节点分别为key和valueLIST包含一个孩子节点类型为该LIST的成员类型UNION一般不怎么用得到。每一个Schema树的根节点为一个Struct类型所有的column按照树的中序遍历顺序编号。
ORC只需要存储schema树中叶子节点的值而中间的非叶子节点只是做一层代理它们只需要负责孩子节点值得读取只有真正的叶子节点才会读取数据然后交由父节点封装成对应的数据结构返回。
文件结构
和Parquet类似ORC文件也是以二进制方式存储的所以是不可以直接读取ORC文件也是自解析的它包含许多的元数据这些元数据都是同构ProtoBuffer进行序列化的。ORC的文件结构入图6其中涉及到如下的概念
ORC文件保存在文件系统上的普通二进制文件一个ORC文件中可以包含多个stripe每一个stripe包含多条记录这些记录按照列进行独立存储对应到Parquet中的row group的概念。文件级元数据包括文件的描述信息PostScript、文件meta信息包括整个文件的统计信息、所有stripe的信息和文件schema信息。stripe一组行形成一个stripe每次读取文件是以行组为单位的一般为HDFS的块大小保存了每一列的索引和数据。stripe元数据保存stripe的位置、每一个列的在该stripe的统计信息以及所有的stream类型和位置。row group索引的最小单位一个stripe中包含多个row group默认为10000个值组成。stream一个stream表示文件中一段有效的数据包括索引和数据两类。索引stream保存每一个row group的位置和统计信息数据stream包括多种类型的数据具体需要哪几种是由该列类型和编码方式决定。图6 ORC文件结构 在ORC文件中保存了三个层级的统计信息分别为文件级别、stripe级别和row group级别的他们都可以用来根据Search ARGuments谓词下推条件判断是否可以跳过某些数据在统计信息中都包含成员数和是否有null值并且对于不同类型的数据设置一些特定的统计信息。
数据访问
读取ORC文件是从尾部开始的第一次读取16KB的大小尽可能的将Postscript和Footer数据都读入内存。文件的最后一个字节保存着PostScript的长度它的长度不会超过256字节PostScript中保存着整个文件的元数据信息它包括文件的压缩格式、文件内部每一个压缩块的最大长度(每次分配内存的大小)、Footer长度以及一些版本信息。在Postscript和Footer之间存储着整个文件的统计信息(上图中未画出)这部分的统计信息包括每一个stripe中每一列的信息主要统计成员数、最大值、最小值、是否有空值等。
接下来读取文件的Footer信息它包含了每一个stripe的长度和偏移量该文件的schema信息(将schema树按照schema中的编号保存在数组中)、整个文件的统计信息以及每一个row group的行数。
处理stripe时首先从Footer中获取每一个stripe的其实位置和长度、每一个stripe的Footer数据(元数据记录了index和data的的长度)整个striper被分为index和data两部分stripe内部是按照row group进行分块的(每一个row group中多少条记录在文件的Footer中存储)row group内部按列存储。每一个row group由多个stream保存数据和索引信息。每一个stream的数据会根据该列的类型使用特定的压缩算法保存。在ORC中存在如下几种stream类型
PRESENT每一个成员值在这个stream中保持一位(bit)用于标示该值是否为NULL通过它可以只记录部位NULL的值DATA该列的中属于当前stripe的成员值。LENGTH每一个成员的长度这个是针对string类型的列才有的。DICTIONARY_DATA对string类型数据编码之后字典的内容。SECONDARY存储Decimal、timestamp类型的小数或者纳秒数等。ROW_INDEX保存stripe中每一个row group的统计信息和每一个row group起始位置信息。
在初始化阶段获取全部的元数据之后可以通过includes数组指定需要读取的列编号它是一个boolean数组如果不指定则读取全部的列还可以通过传递SearchArgument参数指定过滤条件根据元数据首先读取每一个stripe中的index信息然后根据index中统计信息以及SearchArgument参数确定需要读取的row group编号再根据includes数据决定需要从这些row group中读取的列通过这两层的过滤需要读取的数据只是整个stripe多个小段的区间然后ORC会尽可能合并多个离散的区间尽可能的减少I/O次数。然后再根据index中保存的下一个row group的位置信息调至该stripe中第一个需要读取的row group中。
由于ORC中使用了更加精确的索引信息使得在读取数据时可以指定从任意一行开始读取更细粒度的统计信息使得读取ORC文件跳过整个row groupORC默认会对任何一块数据和索引信息使用ZLIB压缩因此ORC文件占用的存储空间也更小这点在后面的测试对比中也有所印证。
在新版本的ORC中也加入了对Bloom Filter的支持它可以进一步提升谓词下推的效率在Hive 1.2.0版本以后也加入了对此的支持。
性能测试
为了对比测试两种存储格式我选择使用TPC-DS数据集并且对它进行改造以生成宽表、嵌套和多层嵌套的数据。使用最常用的Hive作为SQL引擎进行测试。
测试环境
Hadoop集群物理测试集群四台DataNode/NodeManager机器每个机器32core128GB测试时使用整个集群的资源。HiveHive 1.2.1版本使用hiveserver2启动本机MySql作为元数据库jdbc方式提交查询SQL数据集100GB TPC-DS数据集选取其中的Store_Sales为事实表的模型作为测试数据查询SQL选择TPC-DS中涉及到上述模型的10条SQL并对其进行改造。
测试场景和结果
整个测试设置了四种场景每一种场景下对比测试数据占用的存储空间的大小和相同查询执行消耗的时间对比除了场景一基于原始的TPC-DS数据集外其余的数据都需要进行数据导入同时对比这几个场景的数据导入时间。
场景一一个事实表、多个维度表复杂的join查询。
基于原始的TPC-DS数据集。
Store_Sales表记录数287,997,024表大小为
原始Text格式未压缩 : 38.1 GORC格式默认压缩ZLIB,一共1800个分区 : 11.5 GParquet格式默认压缩Snappy一共1800个分区 14.8 G
查询测试结果 场景二维度表和事实表join之后生成的宽表只在一个表上做查询。
整个测试设置了四种场景每一种场景下对比测试数据占用的存储空间的大小和相同查询执行消耗的时间对比除了场景一基于原始的TPC-DS数据集外其余的数据都需要进行数据导入同时对比这几个场景的数据导入时间。选取数据模型中的store_sales, household_demographics, customer_address, date_dim, store表生成一个扁平式宽表(store_sales_wide_table)基于这个表执行查询由于场景一种选择的query大多数不能完全match到这个宽表所以对场景1中的SQL进行部分改造。
store_sales_wide_table表记录数263,704,266表大小为
原始Text格式未压缩 149.0 GORC格式默认压缩 10.6 GPARQUET格式默认压缩 12.5 G
查询测试结果 场景三复杂的数据结构组成的宽表struct、list、map等1层
整个测试设置了四种场景每一种场景下对比测试数据占用的存储空间的大小和相同查询执行消耗的时间对比除了场景一基于原始的TPC-DS数据集外其余的数据都需要进行数据导入同时对比这几个场景的数据导入时间。在场景二的基础上将维度表除了store_sales表转换成一个struct或者map对象源store_sales表中的字段保持不变。生成有一层嵌套的新表store_sales_wide_table_one_nested使用的查询逻辑相同。
store_sales_wide_table_one_nested表记录数263,704,266表大小为
原始Text格式未压缩 245.3 GORC格式默认压缩 10.9 G 比store_sales表还小PARQUET格式默认压缩 29.8 G
查询测试结果 场景四复杂的数据结构多层嵌套。3层
整个测试设置了四种场景每一种场景下对比测试数据占用的存储空间的大小和相同查询执行消耗的时间对比除了场景一基于原始的TPC-DS数据集外其余的数据都需要进行数据导入同时对比这几个场景的数据导入时间。在场景三的基础上将部分维度表的struct内的字段再转换成struct或者map对象只存在struct中嵌套map的情况最深的嵌套为三层。生成一个多层嵌套的新表store_sales_wide_table_more_nested使用的查询逻辑相同。
该场景中只涉及一个多层嵌套的宽表没有任何分区字段store_sales_wide_table_more_nested表记录数263,704,266表大小为
原始Text格式未压缩 222.7 GORC格式默认压缩 10.9 G 比store_sales表还小PARQUET格式默认压缩 23.1 G 比一层嵌套表store_sales_wide_table_one_nested要小
查询测试结果 结果分析
从上述测试结果来看星状模型对于数据分析场景并不是很合适多个表的join会大大拖慢查询速度并且不能很好的利用列式存储带来的性能提升在使用宽表的情况下列式存储的性能提升明显ORC文件格式在存储空间上要远优于Text格式较之于PARQUET格式有一倍的存储空间提升在导数据insert into table select 这样的方式方面ORC格式也要优于PARQUET在最终的查询性能上可以看到无论是无嵌套的扁平式宽表或是一层嵌套表还是多层嵌套的宽表两者的查询性能相差不多较之于Text格式有2到3倍左右的提升。
另外通过对比场景二和场景三的测试结果可以发现扁平式的表结构要比嵌套式结构的查询性能有所提升所以如果选择使用大宽表则设计宽表的时候尽可能的将表设计的扁平化减少嵌套数据。
通过这三种文件存储格式的测试对比ORC文件存储格式无论是在空间存储、导数据速度还是查询速度上表现的都较好一些并且ORC可以一定程度上支持ACID操作社区的发展目前也是Hive中比较提倡使用的一种列式存储格式另外本次测试主要针对的是Hive引擎所以不排除存在Hive与ORC的敏感度比PARQUET要高的可能性。
总结
本文主要从数据模型、文件格式和数据访问流程等几个方面详细介绍了Hadoop生态圈中的两种列式存储格式——Parquet和ORC并通过大数据量的测试对两者的存储和查询性能进行了对比。对于大数据场景下的数据分析需求使用这两种存储格式总会带来存储和性能上的提升但是在实际使用时还需要针对实际的数据进行选择。另外由于不同开源产品可能对不同的存储格式有特定的优化所以选择时还需要考虑查询引擎的因素。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/89149.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!