初识hive数据仓

本文内容来自paper:Hive – A Petabyte Scale Data Warehouse Using Hadoop
使我,对hive有了初步的认识了解。后续继续深入学习

摘要

在商业人工智能行业中,正在收集和分析的数据集的规模正在迅速增长,使得传统的数据仓储解决方案的成本过高。Hadoop是一个流行的开源的map-reduce实现,它被雅虎、Facebook等公司用来存储和处理超大数据集在普通计算机上。然而,map-reduce编程模型是低level,需要开发人员编写定制的程序,这些程序很难维护和重用。Hive是基于Hadoop的开源数据仓库解决方案。hive支持用类似SQL的查询语言hiveql表示的查询,hiveql编译成map-reduce作业,使用hadoop执行。此外,hiveql允许用户将自定义map-reduce脚本插入到查询中。该语言包括一个类型系统,支持包含基元类型的表、数组和映射等集合以及它们的嵌套组合。底层IO库可以扩展为以自定义格式查询数据。Hive还包括一个包含schema和统计信息的系统目录-Metastore,它在数据探索、查询优化和查询编译中非常有用。在Facebook中,Hive仓库包含数万个表和存储超过700TB的数据,并被每月200多个用户广泛用于点对点的分析。

介绍

对大型数据集的可伸缩性分析一直是Facebook许多团队的核心功能——无论是工程团队还是非工程团队。除了公司分析师使用的点对点分析和智能应用程序外,许多Facebook产品也基于分析。这些产品包括简单的报告应用程序,如Facebook广告网络的洞察能力,以及更高级的产品,如Facebook的词典产品。因此,灵活的基础设施能够满足这些不同的应用程序和用户的需求,并且随着Facebook上生成的数据量不断增加,以经济有效的方式进行扩展,这一点至关重要。Hive和Hadoop是我们在Facebook上用来满足这些需求的技术。

2008年之前,Facebook的整个数据处理基础设施是围绕使用商业RDBMS构建的数据仓库而构建的。我们的数据增长非常快——例如,我们从2007年的15TB数据集增长到今天的700TB数据集。当时的基础设施非常不足,一些日常数据处理工作需要一天多的时间来处理,而且情况每况愈下。我们迫切需求也随着我们的数据增长而增长。因此,我们开始探索Hadoop作为一种技术来满足我们的扩展需求。Hadoop已经是一个开放源代码项目,它正以千兆字节的规模使用,并提供使用商品(普通)硬件的可伸缩性,这一事实对我们来说是一个非常引人注目的提议。同样的工作过去花费了一天多的时间完成,现在可以用Hadoop在几个小时内完成。

然而,对终端用户来说,使用Hadoop并不容易,特别是对于那些不熟悉map-reduce的用户。终端用户必须为简单的任务编写map reduce程序,例如获取原始计数或平均值。Hadoop缺乏像SQL这样流行的查询语言的表现力,结果用户花费了数小时来编写程序,以便进行更简单的分析。我们很清楚,为了真正使公司能够更有效地分析这些数据,我们必须改进Hadoop的查询功能。让这些数据更接近用户,这是我们在2007年1月创建Hive的原因。我们的目标是将熟悉的表、列、分区和SQL子集概念引入到Hadoop的非结构化世界中,同时保持Hadoop所享受的扩展性和灵活性。Hive是2008年8月开放源代码,此后许多Hadoop用户根据其数据处理需求对其进行了使用和探索。

从一开始,hive就很受facebook所有用户的欢迎。现在,我们定期在Hadoop/Hive集群上运行数千个作业,数百个用户使用各种各样的应用程序,从简单的汇总作业到商务智能、机器学习应用程序,还支持Facebook产品功能。

在下面的部分中,我们提供了关于Hive体系结构和功能的更多详细信息。第二节描述了数据模型、类型系统和hiveql。第三节详细介绍了如何将配置单元表,将数据存储在底层分布式文件系统HDFS(Hadoop文件系统)中。第四节描述了系统架构和Hive的各种组件。

DATA MODEL, TYPE SYSTEM AND QUERY LANGUAGE(数据模型,类型系统,查询语言)

hive将数据 结构化到熟悉的数据库概念中,如表、列、行和分区(ables, columns, rows, and partitions)。它支持所有主要的元类型——整数、浮点数、双精度数和字符串——以及复杂的类型,如映射map、列表list和结构struct。后者可以任意嵌套以构造更复杂的类型。此外,hive允许用户使用自己的类型和功能扩展系统。查询语言与SQL非常相似,因此任何熟悉SQL的人都可以很容易地理解它。数据模型、类型系统和hiveql中存在一些与传统数据库不同的细微差别,这些细微差别是由Facebook的经验所开发的。我们将在本节中重点介绍这些和其他细节。

数据模型和类型系统

与传统的数据库类似,hive在表中存储数据,其中每个表由若干行组成,每一行由指定数量的列组成。每列都有一个关联的类型。类型要么是元类型,要么是复杂类型。目前,支持以下原始类型:

  • Integers – bigint(8 bytes), int(4 bytes), smallint(2 bytes), tinyint(1 byte). All integer types are signed.(整数型数)
  • Floating point numbers – float(single double(double precision) (浮点型数)
  • String(字符串)

Hive本身也支持以下复杂类型:

  • Associative arrays – map<key-type, value-type>(map, 组合数组)
  • Lists – list (列表)
  • Structs – struct<file-name: field-type, … > (结构体)

这些复杂类型是模板化的,可以通过组合来生成任意复杂的类型。例如,list<map<string,struc<p1:int,p2:int>>表示将字符串映射到结构的关联数组列表,这些数组依次包含两个名为p1和p2的整数字段。这些都可以放在一个create table语句中,以创建具有所需模式的表。例如,下面的语句使用复杂的schema创建Table T1。

CREATE TABLE t1(st string, fl float, li list<map<string, struct<p1:int, p2:int>>);

查询表达式可以使用“.”运算符访问结构内的字段。可以使用“[]”运算符访问关联数组和列表中的值。在上一个示例中,T1.li[0]给出列表的第一个元素,T1.li[0][‘key’]给出与该关联数组中的’key’关联的结构。最后,这个结构的p2字段可以被T1.li[0][‘key’].p2访问。通过这些构造,Hive能够支持任意复杂的结构。

以上面描述的方式创建的表使用hive中已经存在的默认序列化程序和反序列化程序进行序列化和反序列化。但是,有些情况下表的数据是由其他程序准备的,甚至可能是遗留数据。Hive提供了将数据合并到表中而不必转换数据的灵活性,这可以为大型数据集节省大量时间。正如我们在后面的章节中所描述的,这可以通过提供一个jar来实现针对hive的SerDE Java接口。在这种情况下,类型信息也可以由JAR提供,通过ObjectInspector Java接口的相应实现,并通过SerDE接口中呈现的getObjectInspector方法公开该实现。有关这些接口的更多详细信息可以在Hive wiki上找到,但这里的基本要点是,通过提供包含serde和ObjectInspector接口实现的jar,可以将其中编码的任意数据格式和类型插入Hive。Hive中支持的所有本机SerDe和复杂类型也是这些接口的实现。因此,一旦在表和JAR之间建立了适当的关联,查询层就可以将它们与本机类型和格式同等对待。例如,下面的语句向分布式缓存([4])添加一个包含serde和objectinspector接口的jar,以便Hadoop可以使用它,然后继续使用自定义serde创建表。

add jar /jars/myformat.jar;
CREATE TABLE t2
ROW FORMAT SERDE 'com.myformat.MySerDe';

请注意,如果可能,也可以通过组合复杂类型和元类型来提供table schema。

查询语言(Query Language)

hive查询语言(hiveql)由一个子集SQL和一些在我们的环境中有用的扩展组成。传统的SQL特性,如FROM子句子查询、各种类型的join(inner, left outer, right outer 和 outer joins)、cartesian products、Group Bys和aggregations、Union All、Create Table as Select以及许多关于元和复杂类型的有用函数,使语言非常像SQL。实际上,对于前面提到的许多构造,它与SQL完全相同。这使熟悉SQL的任何人都可以启动hive cli(command line interface)并立即开始查询系统。还提供了一些有用的元数据浏览功能,如显示表和描述(desc),以及用于检查查询计划的解释计划功能(尽管这些计划看起来与在传统RDBMS中看到的非常不同)。有一些限制,例如,在联接谓词中只支持相等谓词,并且必须使用ansi联接语法指定联接,例如:

SELECT t1.a1 as c1, t2.b1 as c2 FROM t1 JOIN t2 ON (t1.a2 = t2.b2);

区别于传统的:

SELECT t1.a1 as c1, t2.b1 as c2 FROM t1, t2 WHERE t1.a2 = t2.b2;

另一个限制是如何进行插入。hive当前不支持插入到现有的表或数据分区,所有插入都将覆盖现有的数据。因此,我们在语法中明确如下:

INSERT OVERWRITE TABLE t1
SELECT * FROM t2;

实际上,这些限制并不是问题。我们很少看到这样一种情况:查询不能表示为equi-join,而且由于大多数数据每天或每小时都加载到我们的仓库中,所以我们只需将数据加载到该天或该小时表的新分区中。但是,我们确实认识到,随着加载频率的增加,分区的数量可能会变得非常大,这可能需要我们实现INSERT INTO。另一方面,没有在hive中实现INSERT INTO, UPDATE 和 DELETE,这使得我们可以使用非常简单的机制来处理读写器并发性,而不需要实现复杂的锁定协议。

除了这些限制之外,hiveql还有一些扩展来支持用户用map-reduce程序和他们选择的编程语言表示的分析。这使高级用户能够以map-reduce程序的形式表达复杂的逻辑,这些程序无缝地插入到hiveql查询中。有时,这可能是唯一合理的方法,例如在有Python或PHP库或用户希望用于数据转换的任何其他语言的情况下。例如,文档表上的规范字数示例可以使用map-reduce以下方式表示:

FROM (
MAP doctext USING 'python wc_mapper.py' AS (word, cnt) FROM docs
CLUSTER BY word
)a
REDUCE word, cnt USING 'python wc_reduce.py';

如本例所示,map子句指示如何使用用户程序(在本例中为“python wc_mapper.py”)将输入列(doctext)转换为输出列(word和cnt)。子查询中的cluster by子句指定了散列的输出列,以便将数据分布到reducers,最后reduce子句指定要在子查询的输出列上调用的用户程序(在本例中为python wc_reduce.py)。有时,映射器和reducer之间的分布标准需要向reducer提供数据,以便按照一组不同于用于进行分布的列进行排序。例如,会话中的所有操作都需要按时间排序。hive提供了distribute by和sort by子句来完成此操作,如下面的示例所示:

FROM (
FROM session_table
SELECT sessionid, tstamp, data DISTRIBUTE BY sessionid SORT BY tstamp
)a
REDUCE sessionid, tstamp, data USING 'session_reducer.sh';

注意,在上面的示例中,没有map子句被指示输入列没有被转换。同样,在reduce阶段不进行任何数据转换的情况下,可以使用不带reduce子句的map子句。同样在上面所示的示例中,FROM子句出现在select子句之前,这是与标准SQL语法的另一个偏差。hive允许用户在给定的子查询中交换from和select/map/reduce子句的顺序。这在处理多个插入时变得特别有用和直观。hiveql支持将不同的转换结果作为同一查询的一部分插入到不同的表、分区、hdfs或本地目录中。此功能有助于减少对输入数据进行的扫描次数,如下例所示:

FROM t1
INSERT OVERWRITE TABLE t2 SELECT t3.c2, count(1)
FROM t3
WHERE t3.c1 <= 20
GROUP BY t3.c2
INSERT OVERWRITE DIRECTORY '/output_dir' SELECT t3.c2, avg(t3.c1)
FROM t3
WHERE t3.c1 > 20 AND t3.c1 <= 30
GROUP BY t3.c2
INSERT OVERWRITE LOCAL DIRECTORY '/home/dir' SELECT t3.c2, sum(t3.c1)
FROM t3
WHERE t3.c1 > 30
GROUP BY t3.c2;

在本例中,表T1的不同部分被聚合并用于生成表T2、HDFS目录(/output_dir)和本地目录(/home/dir,位于用户的计算机上)。

III. DATA STORAGE, SERDE AND FILE FORMATS(数据存储,序列化和反序列化,文件格式等)

数据存储

虽然表是hive中的逻辑数据单元,但是表元数据将表中的数据与HDFS目录关联起来。HDFS命名空间中的主要数据单元及其映射如下:

•Tables––表存储在HDFS的目录中。

•Partitions–表的分区(块)存储在表的子目录中。

•bucket–存储桶存储在分区或表目录中的文件中,具体取决于表是分区表或者不是。

例如,test_table表被映射到hdfs中的<warehouse_root_directory>/test_table。仓库根目录由hive-site.xml中的hive.metastore.warehouse.dir配置参数指定。默认情况下,此参数的值设置为/user/hive/warehouse。

一个表可能分区也可能不,一个分区的表被创建通过指定的PARTITIONED BY 句子,在CREATE TABLE语句中,如下示例:

CREATE TABLE test_part(c1 string, c2 int) PARTITIONED BY (ds string, hr int);

在上面所示的示例中,表分区将存储在HDFS中的/user/hive/warehouse/test_part目录中。用户指定的每个不同的ds和hr值都存在一个分区。**请注意,分区列不是表数据的一部分,**分区列值编码在该分区的目录路径中(它们也存储在表元数据中)。可以通过insert语句或通过向表中添加分区的alter语句创建新分区。以下两个陈述:

INSERT OVERWRITE TABLE
test_part PARTITION(ds='2009-01-01', hr=12)
SELECT * FROM t;

ALTER TABLE test_part
ADD PARTITION(ds='2009-02-02', hr=11);

补充:Partition 对应于数据库中的 Partition 列的密集索引,但是 Hive 中 Partition 的组织方式和数据库中的很不相同。在 Hive 中,表中的一个 Partition 对应于表下的一个目录,所有的 Partition 的数据都存储在对应的目录中。例如:pvs 表中包含 ds 和 city 两个 Partition,则对应于 ds = 20090801, city = US 的 HDFS 子目录为:/wh/pvs/ds=20090801/city=US;对应于 ds = 20090801, city = CA 的 HDFS 子目录为;/wh/pvs/ds=20090801/city=CA

向TABLE test_part添加新分区。insert语句还使用表t中的数据填充分区,其中,当alter表创建空分区时。这两个语句最终都会在表的hdfs目录中创建相应的目录-/user/hive/warehouse/test_part/ds=2009-01-01/hr=12和/user/hive/warehouse/test_part/ds=2009-02-02/hr=11。如果分区值包含字符(如/或):HDFS使用这些字符来表示目录结构,那么这种方法确实会造成一些复杂情况,但是正确地转义这些字符确实需要生成与HDFS兼容的目录名。

Hive编译器能够使用这些信息来精简需要扫描数据的目录,以便评估查询。如果是测试部件表,则查询:

SELECT * FROM test_part WHERE ds='2009-01-01';

将仅仅扫描 /user/hive/warehouse/test_part/ds=2009-01-01 目录 并查询:

SELECT * FROM test_part
WHERE ds='2009-02-02' AND hr=11;

将只扫描/user/hive/warehouse/test_part/ds=2009-01-01/hr=12目录中的所有文件。精简数据对处理查询所需的时间有重大影响。在许多方面,这种分区方案类似于许多数据库供应商所称的列表分区,但不同之处在于分区键的值存储在元数据中而不是数据中。

Hive使用的最终存储单元概念是Buckets的概念。bucket是表或分区的叶级目录中的文件。在创建表时,用户可以指定所需的存储桶数量和存储数据的列。在当前的实现中,此信息用于在用户对数据样本执行查询时对数据进行精简,例如,将一个表放入32个存储桶中,通过选择查看第一个数据存储桶,可以快速生成1/32的样本。同样,声明:

SELECT * FROM t TABLESAMPLE(2 OUT OF 32);

会扫描第二个存储桶中的数据。请注意,确保bucket文件正确创建和命名的职责是应用程序的责任,hiveql ddl语句当前不尝试以与表属性兼容的方式对数据进行bucket。因此,应谨慎使用桶形信息。

虽然与表相对应的数据始终位于HDFS中的<warehouse_root_directory>/test_table location中,但是hive还允许用户查询存储在HDFS中其他位置的数据。这可以通过外部表子句实现,如下面的示例所示。

CREATE EXTERNAL TABLE test_extern(c1 string, c2 int)
 LOCATION '/user/mytables/mydata';

使用此语句,用户可以指定EXTERNAL TABLE是一个外部表,每行由两列组成—C1和C2。此外,数据文件存储在HDFS中的位置/user/mytables/mydata中。请注意,由于没有定义自定义SERDE,因此假定数据是配置单元的内部格式。外部表与普通表的不同之处在于,外部表上的drop table命令只删除表元数据,不删除任何数据。另一方面,在普通表上的放置也会删除与该表相关联的数据。

B. Serialization/Deserialization(SerDe)(序列化与反序列化)

如前所述,HIVE可以实现由用户提供的SerDe Java接口的实现,并将其与表或分区相关联。因此,可以很容易地解释和查询自定义数据格式。hive中默认的serde实现称为lazyserde–它将延迟地反序列化为内部对象,因此只有在某些查询表达式中需要行的列时,才会产生列的反序列化成本。LazySerde假定数据存储在文件中,这样行由换行符(ASCII代码13)分隔,行中的列由ctrl-a(ASCII代码1)分隔。此serde还可以用于读取在列之间使用任何其他分隔符的数据。

例如,该语句

CREATE TABLE test_delimited(c1 string, c2 int) ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\002' LINES TERMINATED BY '\012';

指定table_test的数据分割使用ctrl-b(ASCII代码2)作为列分隔符,并使用ctrl-l(ASCII代码12)作为行分隔符。此外,可以指定分隔符来分隔映射的序列化键和值,也可以指定不同的分隔符来分隔列表(集合)的各个元素。下面的陈述说明了这一点。

CREATE TABLE test_delimited2(c1 string,
c2 list<map<string, int>>)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\002' COLLECTION ITEMS TERMINATED BY '\003' MAP KEYS 
		  TERMINATED BY '\004';

除了Lazyserde之外,还存在一些其他有趣的serde,这些serde位于hive_contrib.jar,也提供分布式的。一个特别有用的是regexserde,它允许用户指定一个正则表达式来解析行中的各个列。例如,可以使用以下语句来解释Apache日志。

add jar 'hive_contrib.jar'; CREATE TABLE apachelog(
host string, 
identity string, 
user string, 
time string,
request string, 
status string, 
size string, 
referer string, 
agent string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.contrib.serde2.RegexSerDe'
WITH SERDEPROPERTIES(
'input.regex' = '([^ ]*) ([^ ]*) ([^ ]*) (-|\\[[^\\]]*\\]) ([^
\"]*|\"[^\"]*\") (-|[0-9]*) (-|[0-9]*)(?: ([^ \"]*|\"[^\"]*\") ([^ \"]*|\"[^\"]*\"))?',
'output.format.string' = '%1$s %2$s %3$s %4$s %5$s %6$s %7$s %8$s %9$s');

input.regex属性是应用于每个记录的正则表达式,output.format.string指示如何从正则表达式中匹配的组构造列字段。此示例还说明如何使用WITH SERDEProperties子句将任意键值对传递给SERDE,这是一种非常有用的功能,可以将任意参数传递给自定义SERDE。

C.文件格式

Hadoop文件可以以不同的格式存储。Hadoop中的文件格式指定记录如何存储在文件中。例如,文本文件存储在TextInputFormat中,binary文件可以存储为SequenceFileInputFormat。用户还可以实现自己的文件格式。配置单元不会对存储数据的文件输入格式的类型施加限制。创建表时可以指定格式。除了上面提到的两种格式外,hive还提供了一个RCFileInputFormat,它以面向列的方式存储数据。这样的组织可以提供重要的性能改进,特别是对于不访问表中所有列的查询。用户可以添加自己的文件格式,并将它们关联到表中,如下面的语句所示。

CREATE TABLE dest1(key INT, value STRING) STORED AS
INPUTFORMA T 'org.apache.hadoop.mapred.SequenceFileInputFormat'
OUTPUTFORMA T 'org.apache.hadoop.mapred.SequenceFileOutputFormat'

stored as子句指定用于确定表或分区目录中文件的输入和输出格式的类。这可以是实现FielePosiFrand和FielOutPuttFraseJava接口的任何类。这些类可以在jar中以类似于添加自定义serde示例中所示的方式证明给hadoop。

系统架构和组件

在这里插入图片描述
以下组件是Hive中的主要构建基块:

•Metastore–存储有关表、列、分区等的系统目录和元数据的组件。

•Driver–在hive中移动时管理hiveql语句生命周期的组件。驱动程序还维护会话句柄和任何会话统计信息。

•Query Compiler–将hiveql编译为map/reduce任务的有向非循环图的组件。

•Execution Engine–以适当的依赖顺序执行编译器生成的任务的组件。执行引擎与底层Hadoop实例交互。

•hive server–提供thrift接口和JDBC/ODBC服务器的组件,并提供将hive与其他应用程序集成的方法。

•Clients components 客户端组件,如命令行界面(CLI)、Web UI和JDBC/ODBC驱动程序。

•可扩展性接口,包括前面已经描述过的serde和objectinspector接口,以及用户定义的函数udf和聚合函数udaf接口,这些接口允许用户定义自己的自定义函数。

hiveql语句通过cli、web ui或使用thrift、odbc或jdbc接口的外部客户机提交。驱动程序首先将查询传递给编译器,然后使用Metastore中存储的元数据,在编译器中执行典型的分析、类型检查和语义分析阶段。编译器生成一个逻辑计划,然后通过一个简单的基于规则的优化器进行优化。最后,以DAG映射的形式生成一个优化的计划,减少任务和HDFS任务。然后,执行引擎使用hadoop按照依赖项的顺序执行这些任务。

在本节中,我们将提供有关元存储、查询编译器和执行引擎的更多详细信息。

A.Metastore

Metastore充当hive的系统目录。它存储有关表、表分区、schame、列及其类型、表位置等的所有信息。可以使用Thrift接口查询或修改这些信息,因此可以从不同编程语言的客户机调用这些信息。由于这些信息需要快速地提供给编译器,因此我们选择将这些信息存储在传统的RDBMS上。因此,Metastore成为运行在RDBMS上的应用程序,并使用一个名为DataNucleus的开放源ORM层将对象表示转换为关系模式,反之亦然。我们选择了这种方法,而不是将这些信息存储在HDFS中,因为我们需要Metastore非常低的延迟。数据核层允许我们插入许多不同的RDBMS技术。在Facebook的部署中,我们使用MySQL来存储这些信息。

Metastore对于hive非常重要。如果没有系统目录,就不可能对Hadoop文件施加结构。
因此,定期备份存储在Metastore中的信息非常重要。理想情况下,还应该部署复制服务器,以提供许多生产环境所需的可用性。还必须确保此服务器能够根据用户提交的查询数进行扩展。hive通过确保没有从映射器或作业的缩减器进行Metastore调用来解决这个问题。映射器或还原器所需的任何元数据都通过编译器生成的XML计划文件传递,这些文件包含运行时所需的任何信息。

Metastore中的ORM逻辑可以部署在客户机库中,这样它就可以在客户机端运行,并向RDBMS发出直接调用。如果与Hive交互的客户机只有CLI或Web UI,则此部署很容易开始,而且是理想的部署。然而,一旦HIVE元数据需要被Python、PHP等语言中的程序所操纵和查询,也就是说,没有由Java编写的客户端,就必须部署一个单独的Metastore服务器。

B.QueryCompiler

查询编译器使用存储在Metastore中的元数据来生成执行计划。与传统数据库中的编译器类似,hive编译器按以下步骤处理hiveql语句:

•parse–hive使用Antlr为查询生成抽象语法树(ast)。

•类型检查和语义分析–在此阶段,编译器从Metastore中获取所有输入和输出表的信息,并使用这些信息构建逻辑计划。它检查表达式中的类型兼容性,并在此阶段标记任何编译时语义错误。AST到运算符DAG的转换通过称为查询块(QB)树的中间表示。编译器将嵌套查询转换为QB树中的父子关系。同时,QB树表示也有助于以比普通AST更易于转换为运算符DAG的形式组织AST树的相关部分。

•优化–优化逻辑由一系列转换组成,这样一个转换产生的运算符DAG将作为输入传递给下一个转换。任何希望更改编译器或希望添加新的优化逻辑的人都可以通过将转换实现为转换接口的扩展,并将其添加到优化器中的转换链中,轻松做到这一点。

转换逻辑通常包括在运算符DAG上执行一次行走,以便在满足相关条件或规则时对运算符DAG执行某些处理操作。转换中涉及的五个主要接口是节点、图形浏览器、调度程序、规则和处理器。操作员DAG中的节点实现节点接口。这使得可以使用上面提到的其他接口来操纵操作员DAG。典型的转换包括遍历DAG和访问的每个节点,检查是否满足某个规则,然后在满足后者的情况下为该规则调用相应的处理器。调度器维护从规则到处理器的映射,并进行规则匹配。它被传递给graphwalker,以便在遍历中访问节点时可以调度适当的处理器。图2中的流程图显示了典型转换的结构。

在这里插入图片描述
图2:优化过程中典型变换流程图

作为优化阶段的一部分,以下转换当前在Hive中完成:

i.列精简-此优化步骤确保只有查询处理过程中需要的列实际从行中投影出来。
ii.Predicate Pushdown(谓词下推)–如果可能,将谓词下推到扫描,以便在处理早期筛选行。
iii.分区精简——分区列上的谓词用于修剪不满足谓词的分区的文件。
iv.map端联接-在联接中的某些表非常小的情况下,将在所有映射器中复制小表并与其他表联接。此行为由表单查询中的提示触发:

SELECT /*+ MAPJOIN(t2) */ t1.c1, t2.c1 FROM t1 JOIN t2 ON(t1.c2 = t2.c2);

多个参数控制映射器上用于保存已复制表内容的内存量。这些是hive.mapjoin.size.key和hive.mapjoin.cache.numRows,它们控制随时保存在内存中的表的行数,并为系统提供联接键的大小。

v.连接重新排序-较大的表在减速器中进行流式处理,而较小的表则保存在内存中。这样可以确保联接操作不会超过减速器侧的内存限制。

除了mapjoin提示外,用户还可以提供提示或设置参数来执行以下操作:

i.重新划分数据以处理分组中的偏差——许多现实数据集对常用查询的分组依据子句中使用的列具有幂律分布。在这种情况下,将数据按列分组,然后在reducer中进行聚合的常规计划不起作用,因为大多数数据被发送到极少数reducer。在这种情况下,更好的计划是使用两个map/reduce阶段来计算聚合。在第一阶段,数据随机分布(或在不同聚合的情况下分布在不同列上)到Reducers,并计算部分聚合。然后在第二个map/reduce阶段,将这些部分聚合按列分布到reducer上。由于部分聚合元组的数量远小于基本数据集,因此这种方法通常会带来更好的性能。在配置单元中,可以通过以下方式设置参数来触发此行为:

set hive.groupby.skewindata=true; SELECT t1.c1, sum(t1.c2)
FROM t1
GROUP BY t1;

ii.映射器中基于哈希的部分聚合–基于哈希的部分聚合可能会减少映射器发送给还原器的数据。这反过来又减少了排序和合并这些数据所花费的时间。因此,使用此策略可以获得大量的性能提升。Hive允许用户控制映射器上可用于保存哈希表中的行以进行此优化的内存量。参数hive.map.aggr.hash.percentmemory指定可用于保存哈希表的映射器内存分数,例如0.5将确保一旦哈希表大小超过映射器最大内存的一半,存储在其中的部分聚合将发送到减速器。参数hive.map.aggr.hash.min.reducation还用于控制映射器中使用的内存量。

计划中的节点是物理运算符,边缘表示运算符之间的数据流。每个节点中的最后一行表示该运算符的输出模式。由于缺少空间,我们不描述每个操作员节点中指定的参数。该计划有三个减少就业的计划。

在同一个map reduce作业中,repartition操作符(reducesinkoperator)下面的操作符树部分由映射器执行,上面的部分由reducer执行。重新分区本身由执行引擎执行。

请注意,第一个映射减少了对HDF、TMP1和TMP2的两个临时文件的作业写入,而第二个和第三个映射减少了作业会消耗这些临时文件。

•生成物理计划——优化阶段结束时生成的逻辑计划,然后分为多个map/reduce和hdfs任务。例如,分组依据倾斜数据可以生成两个map/reduce任务,然后生成一个最终的hdfs任务,将结果移动到hdfs中的正确位置。在这个阶段的最后,物理计划看起来像一个任务的DAG,每个任务封装了计划的一部分。

在下面的所有优化之后,我们展示了一个示例多表插入查询及其相应的物理计划。

FROM (SELECT a.status, b.school, b.gender FROM status_updates a JOIN profiles b
ON (a.userid = b.userid
AND a.ds=‘2009-03-20’ )) subq1
INSERT OVERWRITE TABLE gender_summary PARTITION(ds=‘2009-03-20’)
SELECT subq1.gender, COUNT(1) GROUP BY subq1.gender
INSERT OVERWRITE TABLE school_summary PARTITION(ds=‘2009-03-20’)
SELECT subq1.school, COUNT(1) GROUP BY subq1.school

此查询有一个联接,后跟两个不同的聚合。通过将查询写为多表插入,我们确保连接只执行一次。查询计划如下图3所示。

计划中的节点是物理运算符,边缘表示运算符之间的数据流。每个节点中的最后一行表示该运算符的输出模式。由于缺少空间,我们不描述每个操作员节点中指定的参数。该计划有三个减少就业的计划。

在这里插入图片描述

图3:具有3个map/reduce作业的多表插入查询的查询计划

在同一个map reduce作业中,repartition操作符(reducesinkoperator)下面的操作符树部分由映射器执行,上面的部分由reducer执行。重新分区本身由执行引擎执行。

请注意,第一个map reduce job将两个临时文件写入到hdfs、tmp1和tmp2,第二个map reduce jobs和第三个map reduce jobs分别使用这些临时文件。因此,第二和第三个映射减少作业等待第一个映射减少作业完成。

c.执行引擎

最后,这些任务按照它们的依赖关系的顺序执行。只有在所有先决条件都已执行时,才会执行每个依赖任务。map/reduce任务首先将其计划部分序列化为plan.xml文件。然后将此文件添加到任务的作业缓存中,并使用Hadoop生成execmapper和execreducer的实例。这些类中的每一个都反序列化plan.xml并执行操作符dag的相关部分。最终结果存储在临时位置。在整个查询结束时,如果是DML,最终数据将移动到所需的位置。在查询的情况下,数据从临时位置起作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值