本文主要转至:http://www.cnblogs.com/skyl/html
Hadoop 做为MR 的开源实现,一直以动态运行解析文件格式并得到比MPP数据库快上几倍的装载速度为优点。不过,MPP数据库社区也一直批评Hadoop因为文件格式并不是为特定目的而建,所以序列化和反序列化的成本太高。算法
文本格式的数据也是Hadoop中常常碰到的。如TextFile 、XML和JSON。 文本格式除了会占用更多磁盘资源外,对它的解析开销通常会比二进制格式高几十倍以上,尤为是XML 和JSON,它们的解析开销比Textfile 还要大,所以强烈不建议在生产系统中使用这些格式进行储存。 若是须要输出这些格式,请在客户端作相应的转换操做。 文本格式常常会用于日志收集,数据库导入,Hive默认配置也是使用文本格式,并且经常容易忘了压缩,因此请确保使用了正确的格式。另外文本格式的一个缺点是它不具有类型和模式,好比销售金额、利润这类数值数据或者日期时间类型的数据,若是使用文本格式保存,因为它们自己的字符串类型的长短不一,或者含有负数,致使MR没有办法排序,因此每每须要将它们预处理成含有模式的二进制格式,这又致使了没必要要的预处理步骤的开销和储存资源的浪费。数据库
1.TextFileapache
--建立数据表: create table if not exists textfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as textfile; --插入数据: set hive.exec.compress.output=true; --启用压缩格式 set mapred.output.compress=true; set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; --指定输出的压缩格式为Gzip set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; insert overwrite table textfile_table select * from T_Name;
2.SequenceFilejson
值得注意的是,hive读取sequencefile的时候,是把key忽略的,也就是直接读value而且按照指定分隔符分隔字段。可是若是hive的数据来源是从mr生成的,那么写sequencefile的时候,key和value都是有意义的,key不能被忽略,而是应该当成第一个字段。为了解决这种不匹配的状况,有两种办法。一种是要求凡是结果会给hive用的mr job输出value的时候带上key。可是这样的话对于开发是一个负担,读写数据的时候都要注意这个状况。因此更好的方法是第二种,也就是把这个源自于hive的问题交给hive解决,写一个InputFormat包装一下,把value输出加上key便可。缓存
create table if not exists seqfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as sequencefile; --插入数据操做: set hive.exec.compress.output=true; --启用输出压缩格式 set mapred.output.compress=true; set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; --指定输出压缩格式为Gzip set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; SET mapred.output.compression.type=BLOCK; --指定为Block insert overwrite table seqfile_table select * from T_Name;
3.RCFile架构
BG:大多数的Hadoop和Hive存储都是行式存储的,在大多数场景下,这是比较高效的。这种高效归根于以下几点:大多数的表具备的字段个数都不大(通常小于20个);对文件按块进行压缩对于须要处理重复数据的状况比较高效,同时不少的处理和调试工具(如:more,head,awk)均可以很好的应用于行式存储的数据。并发
并不是全部的工具和数据存储都是采用行式存储的方式的,对于特定类型的数据和应用来讲,采用列式存储有时会更好。例如,若是指定的表具备成百上千个字段,而大多数的查询只须要使用到其中的一小部分字段,这是扫描全部的行而过滤掉大部分的数据显然是个浪费。然而,若是数据是按照列而不是行进行存储的话,那么只要对其须要的列进行扫描就能够了,这样能够提升性能。app
对于列式存储而言,进行压缩一般会很是高效,特别是在这列的数据具备较低计算的时候(只有不多的排重值时)。同时,一些列式存储并不须要无力存储NULL值的列。框架
基于这些场景,HIVE中才设计了RCFile.
Hive功能强大的一个方面体如今不一样的存储格式间相互转换数据很是的简单。存储信息存放在了表的元数据信息中。当对表执行一个select查询时。以及向其余表中执行insert操做时,Hive就会使用这个表的元数据信息中提供的内容,而后自动执行转换过程。这样使用能够有多种选择,而不须要额外的程序来对不一样的存储格式进行转换。
能够在建立表时使用:ColumnarSerDe, RCFileInputFormat 和 RCFileOutputFormat; /(end bg)
存储方式:数据按行分块,每块按列存储。结合了行存储和列存储的优势:
RCFile的一个行组包括三个部分:
数据追加:RCFile 不支持任意方式的数据写操做,仅提供一种追加接口,这是由于底层的 HDFS当前仅仅支持数据追加写文件尾部。
行组大小:行组变大有助于提升数据压缩的效率,可是可能会损害数据的读取性能,由于这样增长了 Lazy 解压性能的消耗。并且行组变大会占用更多的内存,这会影响并发执行的其余MR做业。 考虑到存储空间和查询效率两个方面,Facebook 选择 4MB 做为默认的行组大小,固然也容许用户自行选择参数进行配置。
create table if not exists rcfile_table( site string, url string, pv bigint, label string) row format delimited fields terminated by '\t' stored as rcfile; --插入数据操做: set hive.exec.compress.output=true; set mapred.output.compress=true; set mapred.output.compression.codec=org.apache.hadoop.io.compress.GzipCodec; set io.compression.codecs=org.apache.hadoop.io.compress.GzipCodec; insert overwrite table rcfile_table select * from T_Name;
4.ORCFile
相比传统数据库的行式存储引擎,列式存储引擎具备更高的压缩比,更少的IO操做,尤为是在数据列不少,但每次操做仅针对若干列进行查询和计算的情景,列式存储引擎的性价比更高。目前在开源实现中,最有名的列式存储引擎莫过于Parquet和ORC,而且他们都是Apache的顶级项目,在数据存储引擎方面发挥着重要的做用。
自定义格式
ORC(OptimizedRow Columnar) 文件格式存储源自于RC(RecordColumnar File)这种存储格式,RC是一种列式存储引擎,对schema演化(修改schema须要从新生成数据)支持较差,而ORC是对RC改进,但它仍对schema演化支持较差,主要是在压缩编码,查询性能方面作了优化。RC/ORC最初是在Hive中获得使用,最后发展势头不错,独立成一个单独的项目。Hive 1.x版本对事务和update操做的支持,即是基于ORC实现的(其余存储格式暂不支持)。ORC发展到今天,已经具有一些很是高级的feature,好比支持update操做,支持ACID,支持struct,array复杂类型。你可使用复杂类型构建一个相似于parquet的嵌套式数据架构,但当层数很是多时,写起来很是麻烦和复杂,而parquet提供的schema表达方式更容易表示出多级嵌套的数据类型。
Hive中建立表时使用ORC数据存储格式:
create table orc_table (id int,name string) stored as orc;
hive> create table myfile_table(str STRING) > stored as > inputformat 'org.apache.hadoop.hive.contrib.fileformat.base64.Base64TextInputFormat' > outputformat 'org.apache.hadoop.hive.contrib.fileformat.base64.Base64TextOutputFormat'; OK Time taken: 0.399 seconds
hive> load data local inpath '/root/hive/myfile_table' > overwrite into table myfile_table;--加载数据
hive> dfs -text /user/hive/warehouse/myfile_table/myfile_table;--数据文件内容,编码后的格式 aGVsbG8saGl2ZQ== aGVsbG8sd29ybGQ= aGVsbG8saGFkb29w
hive> select * from myfile_table;--使用自定义格式进行解码 OK hello,hive hello,world hello,hadoop Time taken: 0.117 seconds, Fetched: 3 row(s)
5.Parquet列式存储格式 (这部分彻底转至:http://www.infoq.com/cn/articles/in-depth-analysis-of-parquet-column-storage-format/)
Parquet是面向分析型业务的列式存储格式,由Twitter和Cloudera合做开发,2015年5月从Apache的孵化器里毕业成为Apache顶级项目,最新的版本是1.8.0。
列式存储和行式存储相比有哪些优点呢?
当时Twitter的日增数据量达到压缩以后的100TB+,存储在HDFS上,工程师会使用多种计算框架(例如MapReduce, Hive, Pig等)对这些数据作分析和挖掘;日志结构是复杂的嵌套数据类型,例如一个典型的日志的schema有87列,嵌套了7层。因此须要设计一种列式存储格式,既能支持关系型数据(简单数据类型),又能支持复杂的嵌套类型的数据,同时可以适配多种数据处理框架。
关系型数据的列式存储,能够将每一列的值直接排列下来,不用引入其余的概念,也不会丢失数据。关系型数据的列式存储比较好理解,而嵌套类型数据的列存储则会遇到一些麻烦。如图1所示,咱们把嵌套数据类型的一行叫作一个记录(record),嵌套数据类型的特色是一个record中的column除了能够是Int, Long, String这样的原语(primitive)类型之外,还能够是List, Map, Set这样的复杂类型。在行式存储中一行的多列是连续的写在一块儿的,在列式存储中数据按列分开存储,例如能够只读取A.B.C这一列的数据而不去读A.E和A.B.D,那么如何根据读取出来的各个列的数据重构出一行记录呢?
图1 行式存储和列式存储
Google的Dremel系统解决了这个问题,核心思想是使用“record shredding and assembly algorithm”来表示复杂的嵌套数据类型,同时辅以按列的高效压缩和编码技术,实现下降存储空间,提升IO效率,下降上层应用延迟。Parquet就是基于Dremel的数据模型和算法实现的。
Parquet适配多种计算框架
Parquet是语言无关的,并且不与任何一种数据处理框架绑定在一块儿,适配多种语言和组件,可以与Parquet配合的组件有:
查询引擎: Hive, Impala, Pig, Presto, Drill, Tajo, HAWQ, IBM Big SQL
计算框架: MapReduce, Spark, Cascading, Crunch, Scalding, Kite
数据模型: Avro, Thrift, Protocol Buffers, POJOs
那么Parquet是如何与这些组件协做的呢?这个能够经过图2来讲明。数据从内存到Parquet文件或者反过来的过程主要由如下三个部分组成:
1, 存储格式(storage format)
parquet-format项目定义了Parquet内部的数据类型、存储格式等。
2, 对象模型转换器(object model converters)
这部分功能由parquet-mr项目来实现,主要完成外部对象模型与Parquet内部数据类型的映射。
3, 对象模型(object models)
对象模型能够简单理解为内存中的数据表示,Avro, Thrift, Protocol Buffers, Hive SerDe, Pig Tuple, Spark SQL InternalRow等这些都是对象模型。Parquet也提供了一个example object model 帮助你们理解。
例如parquet-mr项目里的parquet-pig项目就是负责把内存中的Pig Tuple序列化并按列存储成Parquet格式,以及反过来把Parquet文件的数据反序列化成Pig Tuple。
这里须要注意的是Avro, Thrift, Protocol Buffers都有他们本身的存储格式,可是Parquet并无使用他们,而是使用了本身在parquet-format项目里定义的存储格式。因此若是你的应用使用了Avro等对象模型,这些数据序列化到磁盘仍是使用的parquet-mr定义的转换器把他们转换成Parquet本身的存储格式。
图2 Parquet项目的结构
Parquet数据模型
理解Parquet首先要理解这个列存储格式的数据模型。咱们以一个下面这样的schema和数据为例来讲明这个问题。
message AddressBook { required string owner; repeated string ownerPhoneNumbers; repeated group contacts { required string name; optional string phoneNumber; } }
这个schema中每条记录表示一我的的AddressBook。有且只有一个owner,owner能够有0个或者多个ownerPhoneNumbers,owner能够有0个或者多个contacts。每一个contact有且只有一个name,这个contact的phoneNumber无关紧要。这个schema能够用图3的树结构来表示。
每一个schema的结构是这样的:根叫作message,message包含多个fields。每一个field包含三个属性:repetition, type, name。repetition能够是如下三种:required(出现1次),optional(出现0次或者1次),repeated(出现0次或者屡次)。type能够是一个group或者一个primitive类型。
Parquet格式的数据类型没有复杂的Map, List, Set等,而是使用repeated fields 和 groups来表示。例如List和Set能够被表示成一个repeated field,Map能够表示成一个包含有key-value 对的repeated field,并且key是required的。
图3 AddressBook的树结构表示
Parquet文件的存储格式
那么如何把内存中每一个AddressBook对象按照列式存储格式存储下来呢?
在Parquet格式的存储中,一个schema的树结构有几个叶子节点,实际的存储中就会有多少column。例如上面这个schema的数据存储实际上有四个column,如图4所示。
图4 AddressBook实际存储的列
Parquet文件在磁盘上的分布状况如图5所示。全部的数据被水平切分红Row group,一个Row group包含这个Row group对应的区间内的全部列的column chunk。一个column chunk负责存储某一列的数据,这些数据是这一列的Repetition levels, Definition levels和values(详见后文)。一个column chunk是由Page组成的,Page是压缩和编码的单元,对数据模型来讲是透明的。一个Parquet文件最后是Footer,存储了文件的元数据信息和统计信息。Row group是数据读写时候的缓存单元,因此推荐设置较大的Row group从而带来较大的并行度,固然也须要较大的内存空间做为代价。通常状况下推荐配置一个Row group大小1G,一个HDFS块大小1G,一个HDFS文件只含有一个块。
图5 Parquet文件格式在磁盘的分布
拿咱们的这个schema为例,在任何一个Row group内,会顺序存储四个column chunk。这四个column都是string类型。这个时候Parquet就须要把内存中的AddressBook对象映射到四个string类型的column中。若是读取磁盘上的4个column要可以恢复出AddressBook对象。这就用到了咱们前面提到的 “record shredding and assembly algorithm”。
Striping/Assembly算法
对于嵌套数据类型,咱们除了存储数据的value以外还须要两个变量Repetition Level(R), Definition Level(D) 才能存储其完整的信息用于序列化和反序列化嵌套数据类型。Repetition Level和 Definition Level能够说是为了支持嵌套类型而设计的,可是它一样适用于简单数据类型。在Parquet中咱们只需定义和存储schema的叶子节点所在列的Repetition Level和Definition Level。
Definition Level
嵌套数据类型的特色是有些field能够是空的,也就是没有定义。若是一个field是定义的,那么它的全部的父节点都是被定义的。从根节点开始遍历,当某一个field的路径上的节点开始是空的时候咱们记录下当前的深度做为这个field的Definition Level。若是一个field的Definition Level等于这个field的最大Definition Level就说明这个field是有数据的。对于required类型的field必须是有定义的,因此这个Definition Level是不须要的。在关系型数据中,optional类型的field被编码成0表示空和1表示非空(或者反之)。
Repetition Level
记录该field的值是在哪个深度上重复的。只有repeated类型的field须要Repetition Level,optional 和 required类型的不须要。Repetition Level = 0 表示开始一个新的record。在关系型数据中,repetion level老是0。
下面用AddressBook的例子来讲明Striping和assembly的过程。
对于每一个column的最大的Repetion Level和 Definition Level如图6所示。
图6 AddressBook的Max Definition Level和Max Repetition Level
下面这样两条record:
AddressBook { owner: "Julien Le Dem", ownerPhoneNumbers: "555 123 4567", ownerPhoneNumbers: "555 666 1337", contacts: { name: "Dmitriy Ryaboy", phoneNumber: "555 987 6543", }, contacts: { name: "Chris Aniszczyk" } } AddressBook { owner: "A. Nonymous" }
以contacts.phoneNumber这一列为例,"555 987 6543"这个contacts.phoneNumber的Definition Level是最大Definition Level=2。而若是一个contact没有phoneNumber,那么它的Definition Level就是1。若是连contact都没有,那么它的Definition Level就是0。
下面咱们拿掉其余三个column只看contacts.phoneNumber这个column,把上面的两条record简化成下面的样子:
AddressBook { contacts: { phoneNumber: "555 987 6543" } contacts: { } } AddressBook { }
这两条记录的序列化过程如图7所示:
图7 一条记录的序列化过程
若是咱们要把这个column写到磁盘上,磁盘上会写入这样的数据(图8):
图8 一条记录的磁盘存储
注意:NULL实际上不会被存储,若是一个column value的Definition Level小于该column最大Definition Level的话,那么就表示这是一个空值。
下面是从磁盘上读取数据并反序列化成AddressBook对象的过程:
1,读取第一个三元组R=0, D=2, Value=”555 987 6543”
R=0 表示是一个新的record,要根据schema建立一个新的nested record直到Definition Level=2。
D=2 说明Definition Level=Max Definition Level,那么这个Value就是contacts.phoneNumber这一列的值,赋值操做contacts.phoneNumber=”555 987 6543”。
2,读取第二个三元组 R=1, D=1
R=1 表示不是一个新的record,是上一个record中一个新的contacts。
D=1 表示contacts定义了,可是contacts的下一个级别也就是phoneNumber没有被定义,因此建立一个空的contacts。
3,读取第三个三元组 R=0, D=0
R=0 表示一个新的record,根据schema建立一个新的nested record直到Definition Level=0,也就是建立一个AddressBook根节点。
能够看出在Parquet列式存储中,对于一个schema的全部叶子节点会被当成column存储,并且叶子节点必定是primitive类型的数据。对于这样一个primitive类型的数据会衍生出三个sub columns (R, D, Value),也就是从逻辑上看除了数据自己之外会存储大量的Definition Level和Repetition Level。那么这些Definition Level和Repetition Level是否会带来额外的存储开销呢?实际上这部分额外的存储开销是能够忽略的。由于对于一个schema来讲level都是有上限的,并且非repeated类型的field不须要Repetition Level,required类型的field不须要Definition Level,也能够缩短这个上限。例如对于Twitter的7层嵌套的schema来讲,只须要3个bits就能够表示这两个Level了。
对于存储关系型的record,record中的元素都是非空的(NOT NULL in SQL)。Repetion Level和Definition Level都是0,因此这两个sub column就彻底不须要存储了。因此在存储非嵌套类型的时候,Parquet格式也是同样高效的。
上面演示了一个column的写入和重构,那么在不一样column之间是怎么跳转的呢,这里用到了有限状态机的知识,详细介绍能够参考Dremel。
数据压缩算法
列式存储给数据压缩也提供了更大的发挥空间,除了咱们常见的snappy, gzip等压缩方法之外,因为列式存储同一列的数据类型是一致的,因此可使用更多的压缩算法。
压缩算法 |
使用场景 |
Run Length Encoding |
重复数据 |
Delta Encoding |
有序数据集,例如timestamp,自动生成的ID,以及监控的各类metrics |
Dictionary Encoding |
小规模的数据集合,例如IP地址 |
Prefix Encoding |
Delta Encoding for strings |
性能
Parquet列式存储带来的性能上的提升在业内已经获得了充分的承认,特别是当大家的表很是宽(column很是多)的时候,Parquet不管在资源利用率仍是性能上都优点明显。具体的性能指标详见参考文档。
Spark已经将Parquet设为默认的文件存储格式,Cloudera投入了不少工程师到Impala+Parquet相关开发中,Hive/Pig都原生支持Parquet。Parquet如今为Twitter至少节省了1/3的存储空间,同时节省了大量的表扫描和反序列化的时间。这两方面直接反应就是节约成本和提升性能。
若是说HDFS是大数据时代文件系统的事实标准的话,Parquet就是大数据时代存储格式的事实标准。
Apache Parquet 最初的设计动机是存储嵌套式数据,好比Protocolbuffer,thrift,json等,将这类数据存储成列式格式,以方便对其高效压缩和编码,且使用更少的IO操做取出须要的数据,这也是Parquet相比于ORC的优点,它可以透明地将Protobuf和thrift类型的数据进行列式存储,在Protobuf和thrift被普遍使用的今天,与parquet进行集成,是一件非容易和天然的事情。 除了上述优点外,相比于ORC, Parquet没有太多其余可圈可点的地方,好比它不支持update操做(数据写成后不可修改),不支持ACID等。
6. Avro
Avro是一种用于支持数据密集型的二进制文件格式。它的文件格式更为紧凑,若要读取大量数据时,Avro可以提供更好的序列化和反序列化性能。而且Avro数据文件天生是带Schema定义的,因此它不须要开发者在API 级别实现本身的Writable对象。最近多个Hadoop 子项目都支持Avro数据格式,如Pig 、Hive、Flume、Sqoop和Hcatalog。
7.示例自定义输入格式:DualInputFormat;
总结:
数据仓库的特色:一次写入、屡次读取,所以,总体来看,ORCFile相比其余格式具备较明显的优点。