背景

     最近在做一个大数据分析平台的项目,项目开发过程中使用spark来计算工作流工程中的每一个计算步骤,多个spark submit计算提交,构成了一个工作流程的计算。其中使用csv来作为多个计算步骤之间的中间结果存储文件,但是csv作为毫无压缩的文本存储方式显然有些性能不够,所以想要寻找一个存储文件效率更高或者执行效率更高的文件格式作为替代品。

 

存储方式
    csv

            csv数据文件属于文本存储方式,spark默认支持,按照行以文本的方式写到文件中,每行一条记录.一般来说文本存储方式无压缩,性能相对较差。

    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文件是以二进制方式存储的,是不可以直接读取和修改的,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文件的格式如下图所示。

上传csv文件 Hadoop HDFS hadoop处理csv文件_元数据

可以看出,存储格式中元数据索引信息是被存储在最后的,所以当读取某一行的数据的时候,就需要去定位最后的索引信息,最后才能去读取对应的行数据。   

 orc

             ORC文件格式是一种Hadoop生态圈中的列式存储格式,它的产生早在2013年初,最初产生自Apache Hive,用于降低Hadoop数据存储空间和加速Hive查询速度。和Parquet类似,它并不是一个单纯的列式存储格式,仍然是首先根据行组分割整个表,在每一个行组内进行按列存储。ORC文件是自描述的,它的元数据使用Protocol Buffers序列化,并且文件中的数据尽可能的压缩以降低存储空间的消耗,目前也被Spark SQL、Presto等查询引擎支持,但是Impala对于ORC目前没有支持,仍然使用Parquet作为主要的列式存储格式。2015年ORC项目被Apache项目基金会提升为Apache顶级项目。
              和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包括多种类型的数据,具体需要哪几种是由该列类型和编码方式决定。

上传csv文件 Hadoop HDFS hadoop处理csv文件_上传csv文件 Hadoop HDFS_02

 和parquet不同的是,每一个row data的开始,都会有index data索引信息,相对于parquet把索引信息放在最后而言,理论上行读取的速度要更快一点。

存储大小(存储效率)

原始数据:

     size:64G/format:csv/location:hdfs

集群运行环境:

     5台/内存:24G/核心:16核

数据相同,从csv转换成了如下不同格式,读写效率如下:

格式    

大小

读写时间

count操作一次时间

csv

36G

3.6min

59s

parquet

6G

1.1min

5s

orc

5.9G

1.2min

5s

avro

13G

1.3min

25s

 

    size:可以看出,相对于csv而言,parque和orc直接缩小了6倍的大小,理论上说可以达到6-10倍的压缩效率,本次使用的parquet默认的gzip压缩方式。

    time:更值得注意的一点是,针对count这种列操作的方法,parquet、orc和avro都获得了相当好的效果,猜测可能是因为不需要这个数据集来计算,而仅仅使用某一些列就能计算,减小了计算的规模。

读写代码

  1.csv太普遍了这里就省略了

  2.parquet:

val df  = sc.read.parquet("path")
df.write.format("parquet").save("hdfs path")

  3.orc:

val df = sc.read.orc("path")
df.write.format("orc").save("hdfs path")

 4.avro:

var data = sc.read.format("com.databricks.spark.avro")
    .option("header", "true")
    .option("mode", "DROPMALFORMED")
    .option("delimiter", ",")
    .load("hdfs://192.168.10.10:9000/data/test/Solderc_to_c.csv")
 
df.write.format("com.databricks.spark.avro").save("hdfs path")

 avro可是使用databricks进行读取,当然比忘了添加databricks的maven依赖

结论

       csv使用较为广泛,多数系统的输入都是csv格式,此外csv文件直接打开是可以理解和读懂的,而且csv文件也是支持末尾添加数据的,但是csv文件因为没有压缩,所以体积较大,某些操作上也不如列式存储。

       parquet使用也较为广泛,默认使用gzip压缩,体积较,运算效率高,但是parquet打开是不能被理解和读懂的,因为其采用二进制存储方式,当系统中无特殊要求,也去需要打开数据文件的时候,为了追求效率可以考虑。

      orc也是列示存储的二进制文件,打开无法被直接理解和读懂,但是因为在文件格式中,每一个row block的开始都有一个轻型索引,所以相较于parquet,orc在检索行的时候,速度要相对较快一点,

其他格式
还有其他的一些格式:indexR/ya100

引用文章如下: