0. 行式存储和列式存储
Hive中常用的存储格式有TEXTFILE 、SEQUENCEFILE、AVRO、RCFILE、ORCFILE、PARQUET等,其中TEXTFILE 、SEQUENCEFILE和AVRO是行式存储,RCFILE、ORCFILE、PARQUET是列式存储。存储格式即是指表的数据是如何在HDFS上组织排列的。
传统的关系型数据库,如 Oracle、DB2、MySQL、SQL SERVER 等采用行式存储(Row-based),在基于行式存储的数据库中, 数据是按照行数据为基础逻辑存储单元进行存储的, 一行中的数据在存储介质中以连续存储形式存在。列式存储(Column-based)是相对于行式存储来说的,新兴的 Hbase、HP Vertica、EMC Greenplum 等分布式数据库均采用列式存储。在基于列式存储的数据库中, 数据是按照列为基础逻辑存储单元进行存储的,一列中的数据在存储介质中以连续存储形式存在。
存在即合理,这两种存储各有其优缺点。
列式存储的优点是:
1.数据在磁盘上是按字段顺序连续组织的;
2.插入insert和更新update时较高效
而其缺点则是牵一发而动全身,即使只是从源表中读取一两个字段,也会读取完整的数据记录。
相反,列式存储的优点就是行式存储的缺点,列式存储的表只会返回想要的字段,投影操作比较高效。
那么,如何确定应该选用哪种存储格式呢?
行式存储的适用场景包括:
1、适合随机的增删改查操作;
2、需要在行中选取所有属性的查询操作;
3、需要频繁插入或更新的操作。
列式存储引擎的适用场景包括:
1、查询过程中,可针对各列的运算并发执行(SMP),最后在内存中聚合完整记录集,最大可能降低查询响应时间;
2、可在数据列中高效查找数据,无需维护索引(任何列都能作为索引),查询过程中能够尽量减少无关IO,避免全表扫描;
3、各列独立存储,且数据类型已知,可以针对该列的数据类型、数据量大小等因素动态选择压缩算法,以提高物理存储利用率。
对于数据仓库和分布式数据库来说,大部分情况下它会从各个数据源汇总数据,然后进行分析和反馈,其操作大多是围绕同一列属性的数据进行的,而当查询某属性的数据记录时,列式数据库只需返回与列属性相关的值,在大数据量查询场景中,列式数据库可在内存中高效组装各列的值,最终形成关系记录集,因此可以显著减少IO消耗,并降低查询响应时间,适合数据仓库和分布式的应用。
2. 主要文件存储格式介绍
2.1 TEXTFILE
TEXTFILE 即正常的文本格式,是Hive默认文件存储格式,因为大多数情况下源数据文件都是以text文件格式保存(便于查看验数和防止乱码)。此种格式的表文件在HDFS上是明文,可用hadoop fs -cat命令查看,从HDFS上get下来后也可以直接读取。
TEXTFILE 存储文件默认每一行就是一条记录,可以指定任意的分隔符进行字段间的分割。但这个格式无压缩,需要的存储空间很大。虽然可结合Gzip、Bzip2、Snappy等使用,使用这种方式,Hive不会对数据进行切分,从而无法对数据进行并行操作。
一般只有与其他系统由数据交互的接口表采用TEXTFILE 格式,其他事实表和维度表都不建议使用。
2.2 SEQUENCEFILE
SequenceFile是Hadoop API 提供的一种二进制文件,它将数据以<key,value>的形式序列化到文件中。这种二进制文件内部使用Hadoop 的标准的Writable 接口实现序列化和反序列化。它与Hadoop API中的MapFile 是互相兼容的。Hive 中的SequenceFile 继承自Hadoop API 的SequenceFile,不过它的key为空,使用value 存放实际的值, 这样是为了避免MR 在运行map 阶段的排序过程。
SequenceFile支持三种压缩选择:NONE, RECORD, BLOCK。 Record压缩率低,一般建议使用BLOCK压缩。 SequenceFile最重要的优点就是Hadoop原生支持较好,有API,但除此之外平平无奇,实际生产中不会使用。
2.3 AVRO
Avro是一种用于支持数据密集型的二进制文件格式。它的文件格式更为紧凑,若要读取大量数据时,Avro能够提供更好的序列化和反序列化性能。并且Avro数据文件天生是带Schema定义的,所以它不需要开发者在API 级别实现自己的Writable对象。动态语言友好,Avro提供的机制使动态语言可以方便地处理Avro数据。最近多个Hadoop 子项目都支持Avro 数据格式,如Pig 、Hive、Flume、Sqoop和Hcatalog。
Avro据说设计出来的最大目的是为了满足模式演进,也是早年间诞生的一种数据格式,使用的也不多。
2.4 RCFILE
RCFile(Record Columnar File)存储结构遵循的是“先水平划分,再垂直划分”的设计理念。RCFile结合了行存储和列存储的优点:首先,RCFile保证同一行的数据位于同一节点,因此元组重构的开销很低;其次,像列存储一样,RCFile能够利用列维度的数据压缩,并且能跳过不必要的列读取。
如上图是HDFS内RCFile的存储结构。每个HDFS块中,RCFile以行组为基本单位来组织记录。。对于一张表,所有行组大小都相同。一个HDFS块会有一个或多个行组。一个行组包括三个部分。第一部分是行组头部的同步标识,主要用于分隔HDFS块中的两个连续行组;第二部分是行组的元数据头部,用于存储行组单元的信息,包括行组中的记录数、每个列的字节数、列中每个域的字节数;第三部分是表格数据段,即实际的列存储数据。
某些列式存储同一列可能存在不同的block上,在查询的时候,Hive重组列的过程会浪费很多IO开销。而RCFile由于相同的列都是在一个HDFS块上,所以相对列存储而言会节省很多资源。
RCFile采用游程编码,相同的数据不会重复存储,很大程度上节约了存储空间,尤其是字段中包含大量重复数据的时候。RCFile不支持任意方式的数据写操作,仅提供一种追加接口,这是因为底层的HDFS当前仅仅支持数据追加写文件尾部。
当处理一个行组时,RCFile无需全部读取行组的全部内容到内存。相反,它仅仅读元数据头部和给定查询需要的列。因此,它可以跳过不必要的列以获得列存储的I/O优势。例如:
select c from table where a>1
针对行组来说,会对一个行组的a列进行解压缩,如果当前列中有a>1的值,然后才去解压缩c。若当前行组中不存在a>1的列,那就不用解压缩c,从而跳过整个行组。
RCFile更多细节可查看官网 RCfile
2.5 ORCFILE
Hive从0.11版本开始提供了ORC的文件格式,ORC文件不仅仅是一种列式文件存储格式,最重要的是有着很高的压缩比,并且对于MapReduce来说是可切分(Split)的。因此,在Hive中使用ORC作为表的文件存储格式,不仅可以很大程度的节省HDFS存储资源,而且对数据的查询和处理性能有着非常大的提升,因为ORC较其他文件格式压缩比高,查询任务的输入数据量减少,使用的Task也就减少了。ORC能很大程序的节省存储和计算资源,但它在读写时候需要消耗额外的CPU资源来压缩和解压缩,当然这部分的CPU消耗是非常少的。
ORC看名字就知道是对RCFile的优化。ORC文件是以二进制方式存储的,所以是不可以直接读取,ORC文件也是自解析的,它包含许多的元数据,这些元数据都是同构ProtoBuffer进行序列化的。ORC的文件结构如下图,其中涉及到如下的概念:
ORC文件:保存在文件系统上的普通二进制文件,一个ORC文件中可以包含多个stripe,每一个stripe包含多条记录,这些记录按照列进行独立存储。
文件级元数据:包括文件的描述信息PostScript、文件meta信息(包括整个文件的统计信息)、所有stripe的信息和文件schema信息。
stripe:一组行形成一个stripe,每次读取文件是以行组为单位的,一般为HDFS的块大小,保存了每一列的索引和数据。
stripe元数据:保存stripe的位置、每一个列的在该stripe的统计信息以及所有的stream类型和位置。
row group:索引的最小单位,一个stripe中包含多个row group,默认为10000个值组成。
stream:一个stream表示文件中一段有效的数据,包括索引和数据两类。
每个ORC文件首先会被横向切分成多个Stripe,而每个Stripe内部以列存储,所有的列存储在一个文件中,而且每个stripe默认的大小是250MB,相对于RCFile默认的行组大小是4MB,所以比RCFile更高效。Hive读取数据的时候,根据FileFooter读出Stripe的信息,根据IndexData读出数据的偏移量从而读取出数据。
2.6 PARQUET
通常我们使用关系数据库存储结构化数据,而关系数据库中使用数据模型都是扁平式的,遇到诸如List、Map和自定义Struct的时候就需要用户在应用层解析。但是在大数据环境下,通常数据的来源是服务端的埋点数据,很可能需要把程序中的某些对象内容作为输出的一部分,而每一个对象都可能是嵌套的,所以如果能够原生的支持这种数据,这样在查询的时候就不需要额外的解析便能获得想要的结果。
Parquet的灵感来自于2010年Google发表的Dremel论文,文中介绍了一种支持嵌套结构的存储格式,并且使用了列式存储的方式提升查询性能。Parquet仅仅是一种存储格式,它是语言、平台无关的,并且不需要和任何一种数据处理框架绑定。这也是parquet相较于orc的仅有优势:支持嵌套结构。Parquet 没有太多其他可圈可点的地方,比如他不支持update操作(数据写成后不可修改),不支持ACID等.
3. 存储性能对比
我在个人电脑上搭建的CDH版本是Hive2.1.1-cdh6.2.1。首先建立不同存储的Hive表,以textfile为例,建表语句如下,其他格式只是换个存储格式。另外,隐去了location信息。
DROP TABLE IF EXISTS `tbl_lannister_test_textfile`;
CREATE EXTERNAL TABLE IF NOT EXISTS `tbl_lannister_test_textfile`(
pri_key string,
field1 string,
field2 string,
field3 string,
field4 string,
field5 string,
field6 string,
field7 string,
field8 string,
field9 string,
field10 string,
field11 string,
field12 string,
field13 string,
field14 string,
field15 bigint,
field16 bigint,
field17 bigint,
field18 bigint,
field19 bigint,
field20 bigint,
field21 bigint)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'
STORED AS TEXTFILE ;
---------------------------------------
STORED AS SEQUENCEFILE;
STORED AS AVRO;
STORED AS RCFILE;
STORED AS ORC;
STORED AS PARQUETFILE;
---------------------------------------
构造一个以逗号分隔的、21个字段的文本文件,数值字段采用自增方式,大小约为1.2G,并用hadoop fs -put导入textfile格式的表中。确定textfile表中有数据后,通过insert overwrite向其他的表插入数据。
INSERT OVERWRITE TABLE TBL_LANNISTER_TEST_SEQUENCEFILE
SELECT *
FROM TBL_LANNISTER_TEST_TEXTFILE;
在所有表都有数据后,用hadoop fs -du -h 表存储路径比较各个格式占用的空间,结果如下
TEXTFILE 3.5 G
SEQUENCEFILE 3.7 G
AVRO 3.6 G
RCFILE 2.9 G
ORC 5.8 M
PARQUETFILE 196.9 M
可以看到ORC压缩存储性能最好,比textfile原始数据缩小了近600倍。parquet的存储空间也非常小,能够压缩15倍左右,而rcfile的压缩性能不尽如人意。其他三种格式可认为是靠眼神压缩。
4. 计算性能对比
在上面insert插入表的过程中,分别用的时间如下
SEQUENCEFILE 44.785 s
AVRO 46.086 s
RCFILE 20.766 s
ORC 22.5 s
PARQUETFILE 263.182 s
很明显ORC的写性能也很好,而明星选手parquet却不知为何表现非常差。
最后对每张表执行以下SQL测试其计算性能:
SELECT pri_key,field1,sum(field18),sum(field19) as summer from TBL_LANNISTER_TEST_TEXTFILE group by pri_key,field1 order by summer;
能够得到以下结果
TEXTFILE 48.294 seconds
SEQUENCEFILE 27.107 seconds
AVRO 78.234 seconds
RCFILE 12.197 seconds
ORC 12.969 seconds
PARQUETFILE 31.038 seconds
没啥说的,orc真是全能战士,读写兼优。建议在Hive中采用orc作为表的存储格式,这和业界的主流选择是一样的。
另外,parquet的表现和社区中宣传的不一样,可能是数据量的原因,以后改变数据量和内容再试试。