共计 6407 个字符,预计需要花费 17 分钟才能阅读完成。
一、ORC File 文件构造
- ORC 是列式存储,有多种文件压缩形式,并且有着很高的压缩比。
- 文件是可切分(Split)的。因而,在 Hive 中应用 ORC 作为表的文件存储格局,不仅节俭 HDFS 存储资源,查问工作的输出数据量缩小,应用的 MapTask 也就缩小了。
- 提供了多种索引,row group index、bloom filter index。
- ORC 能够反对简单的数据结构(比方 Map 等)
列式存储
因为 OLAP 查问的特点,列式存储能够晋升其查问性能,然而它是如何做到的呢?这就要从列式存储的原理说大数据培训起,从图 1 中能够看到,绝对于关系数据库中通常应用的行式存储,在应用列式存储时每一列的所有元素都是顺序存储的。由此特点能够给查问带来如下的优化:
• 查问的时候不须要扫描全副的数据,而只须要读取每次查问波及的列,这样能够将 I / O 耗费升高 N 倍,另外能够保留每一列的统计信息(min、max、sum 等),实现局部的谓词下推。
• 因为每一列的成员都是同构的,能够针对不同的数据类型应用更高效的数据压缩算法,进一步减小 I /O。
• 因为每一列的成员的同构性,能够应用更加适宜 CPU pipeline 的编码方式,减小 CPU 的缓存生效。
须要留神的是,ORC 在读写时候须要耗费额定的 CPU 资源来压缩和解压缩,当然这部分的 CPU 耗费是非常少的。
数据模型
和 Parquet 不同,ORC 原生是不反对嵌套数据格式的,而是通过对简单数据类型非凡解决的形式实现嵌套格局的反对,例如对于如下的 hive 表:
CREATE TABLE orcStructTable
(name
string,course
struct<course:string,score:int>,score
map<string,int>,work_locations
array<string>)在 ORC 的构造中蕴含了简单类型列和原始类型,前者包含 LIST、STRUCT、MAP 和 UNION 类型,后者包含 BOOLEAN、整数、浮点数、字符串类型等,其中 STRUCT 的孩子节点包含它的成员变量,可能有多个孩子节点,MAP 有两个孩子节点,别离为 key 和 value,LIST 蕴含一个孩子节点,类型为该 LIST 的成员类型,UNION 个别不怎么用失去。每一个 Schema 树的根节点为一个 Struct 类型,所有的 column 依照树的中序遍历程序编号。
ORC 只须要存储 schema 树中叶子节点的值,而两头的非叶子节点只是做一层代理,它们只须要负责孩子节点值得读取,只有真正的叶子节点才会读取数据,而后交由父节点封装成对应的数据结构返回。
文件构造
和 Parquet 相似,ORC 文件也是以二进制形式存储的,所以是不能够间接读取,ORC 文件也是自解析的,它蕴含许多的元数据,这些元数据都是同构 ProtoBuffer 进行序列化的。ORC 的文件构造如下图,其中波及到如下的概念:
• 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 包含多种类型的数据,具体须要哪几种是由该列类型和编码方式决定。
在 ORC 文件中保留了三个层级的统计信息,别离为文件级别、stripe 级别和 row group 级别的,他们都能够用来依据 Search ARGuments(谓词下推条件)判断是否能够跳过某些数据,在统计信息中都蕴含成员数和是否有 null 值,并且对于不同类型的数据设置一些特定的统计信息。
(1)file level
在 ORC 文件的开端会记录文件级别的统计信息,会记录整个文件中 columns 的统计信息。这些信息次要用于查问的优化,也能够为一些简略的聚合查问比方 max, min, sum 输入后果。
(2)stripe level
ORC 文件会保留每个字段 stripe 级别的统计信息,ORC reader 应用这些统计信息来确定对于一个查问语句来说,须要读入哪些 stripe 中的记录。比如说某个 stripe 的字段 max(a)=10,min(a)=3,那么当 where 条件为 a >10 或者 a <3 时,那么这个 stripe 中的所有记录在查问语句执行时不会被读入。
(3)row level
为了进一步的防止读入不必要的数据,在逻辑上将一个 column 的 index 以一个给定的值 (默认为 10000,可由参数配置) 宰割为多个 index 组。以 10000 条记录为一个组,对数据进行统计。Hive 查问引擎会将 where 条件中的束缚传递给 ORC reader,这些 reader 依据组级别的统计信息,过滤掉不必要的数据。如果该值设置的太小,就会保留更多的统计信息,用户须要依据本人数据的特点衡量一个正当的值。
数据拜访
读取 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 文件格式时,用户能够应用 HDFS 的每一个 block 存储 ORC 文件的一个 stripe。对于一个 ORC 文件来说,stripe 的大小个别须要设置得比 HDFS 的 block 小,如果不这样的话,一个 stripe 就会别离在 HDFS 的多个 block 上,当读取这种数据时就会产生近程读数据的行为。如果设置 stripe 的只保留在一个 block 上的话,如果以后 block 上的残余空间不足以存储下一个 strpie,ORC 的 writer 接下来会将数据打散保留在 block 残余的空间上,直到这个 block 存满为止。这样,下一个 stripe 又会从下一个 block 开始存储。
因为 ORC 中应用了更加准确的索引信息,使得在读取数据时能够指定从任意一行开始读取,更细粒度的统计信息使得读取 ORC 文件跳过整个 row group,ORC 默认会对任何一块数据和索引信息应用 ZLIB 压缩,因而 ORC 文件占用的存储空间也更小,这点在前面的测试比照中也有所印证。
文件压缩
ORC 文件应用两级压缩机制,首先将一个数据流应用流式编码器进行编码,而后应用一个可选的压缩器对数据流进行进一步压缩。
一个 column 可能保留在一个或多个数据流中,能够将数据流划分为以下四种类型:
• Byte Stream
字节流保留一系列的字节数据,不对数据进行编码。
• Run Length Byte Stream
字节长度字节流保留一系列的字节数据,对于雷同的字节,保留这个反复值以及该值在字节流中呈现的地位。
• Integer Stream
整形数据流保留一系列整形数据。能够对数据量进行字节长度编码以及 delta 编码。具体应用哪种编码方式须要依据整形流中的子序列模式来确定。
• Bit Field Stream
比特流次要用来保留 boolean 值组成的序列,一个字节代表一个 boolean 值,在比特流的底层是用 Run Length Byte Stream 来实现的。
接下来会以 Integer 和 String 类型的字段举例来说明。
(1)Integer
对于一个整形字段,会同时应用一个比特流和整形流。比特流用于标识某个值是否为 null,整形流用于保留该整形字段非空记录的整数值。
(2)String
对于一个 String 类型字段,ORC writer 在开始时会查看该字段值中不同的内容数占非空记录总数的百分比不超过 0.8 的话,就应用字典编码,字段值会保留在一个比特流,一个字节流及两个整形流中。比特流也是用于标识 null 值的,字节流用于存储字典值,一个整形流用于存储字典中每个词条的长度,另一个整形流用于记录字段值。
如果不能用字典编码,ORC writer 会晓得这个字段的反复值太少,用字典编码效率不高,ORC writer 会应用一个字节流保留 String 字段的值,而后用一个整形流来保留每个字段的字节长度。
在 ORC 文件中,在各种数据流的底层,用户能够自选 ZLIB, Snappy 和 LZO 压缩形式对数据流进行压缩。编码器个别会将一个数据流压缩成一个个小的压缩单元,在目前的实现中,压缩单元的默认大小是 256KB。
二、Hive+ORC 建设数据仓库
在建 Hive 表的时候咱们就应该指定文件的存储格局。所以你能够在 Hive QL 语句外面指定用 ORCFile 这种文件格式,如下:
CREATE TABLE … STORED AS ORC
ALTER TABLE … [PARTITION partition_spec] SET FILEFORMAT ORC
SET hive.default.fileformat=Orc 所有对于 ORCFile 的参数都是在 Hive QL 语句的 TBLPROPERTIES 字段外面呈现,他们是:
三、Java 操作 ORC
到 https://orc.apache.org 官网下载 orc 源码包,而后编译获取 orc-core-1.3.0.jar、orc-mapreduce-1.3.0.jar、orc-tools-1.3.0.jar,将其退出我的项目中
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hive.ql.exec.vector.LongColumnVector;
import org.apache.hadoop.hive.ql.exec.vector.VectorizedRowBatch;
import org.apache.orc.CompressionKind;
import org.apache.orc.OrcFile;
import org.apache.orc.TypeDescription;
import org.apache.orc.Writer;
public class TestORCWriter {
public static void main(String[] args) throws Exception {Path testFilePath = new Path("/tmp/test.orc");
Configuration conf = new Configuration();
TypeDescription schema = TypeDescription.fromString("struct<field1:int,field2:int,field3:int>");
Writer writer = OrcFile.createWriter(testFilePath, OrcFile.writerOptions(conf).setSchema(schema).compress(CompressionKind.SNAPPY));
VectorizedRowBatch batch = schema.createRowBatch();
LongColumnVector first = (LongColumnVector) batch.cols[0];
LongColumnVector second = (LongColumnVector) batch.cols[1];
LongColumnVector third = (LongColumnVector) batch.cols[2];
final int BATCH_SIZE = batch.getMaxSize();
// add 1500 rows to file
for (int r = 0; r < 15000000; ++r) {
int row = batch.size++;
first.vector[row] = r;
second.vector[row] = r * 3;
third.vector[row] = r * 6;
if (row == BATCH_SIZE - 1) {writer.addRowBatch(batch);
batch.reset();}
}
if (batch.size != 0) {writer.addRowBatch(batch);
batch.reset();}
writer.close();}
}大多状况下,还是倡议在 Hive 中将文本文件转成 ORC 格局,这种用 JAVA 在本地生成 ORC 文件,属于非凡需要场景。