目录
前言
这篇文章是的目的是尽可能全面地对Hive进行入门介绍,这篇文章是基于hive-2.1.1版本介绍的,这个版本的Hive是运行MapReduce上的。Hive是对数据仓库进行管理和分析数据的工具。Hive与MySQL语法结构相似,但是Hive和MYSQL毕竟不同,执行原理、优化方法,底层架构都完全不相同。 大数据离线分析使用Hive已经成为主流,整理的这个入门级别的文章,希望能给想入门的同学提供一些帮助。同时会通过例子进行解释,使大家直观的了解每个实例运行情况,本文学习和借鉴于原文 http://gitbook.cn/books/5924bd0523245b0aa3776b65/index.html,对该文整理者表示感谢。
一.Hive介绍
Hive是一种用类SQL语句来协助读写、管理那些存储在分布式存储系统上大数据集的数据仓库软件。
官网定义:The Apache Hive ™ data warehouse software facilitates reading, writing, and managing large datasets residing in distributed storage using SQL.
Hive的几个特点:
-
Hive最大的特点是通过类SQL来分析大数据,而避免了写MapReduce程序来分析数据,这样使得分析数据更容易。
-
数据是存储在HDFS上的,Hive本身并不提供数据的存储功能。
-
Hive是将数据映射成数据库和一张张的表,库和表的元数据信息一般存在关系型数据库上(比如MySQL)。
-
数据存储方面:它能够存储很大的数据集,并且对数据完整性、格式要求并不严格。
-
数据处理方面:因为Hive语句最终会生成MapReduce任务去计算,所以不适用于实时计算的场景,它适用于离线分析。
二.Hive架构
Hive的核心:Hive的核心是驱动引擎,驱动引擎由四部分组成:
-
解释器:解释器的作用是将HiveSQL语句转换为语法树(AST)。
-
编译器:编译器是将语法树编译为逻辑执行计划。
-
优化器:优化器是对逻辑执行计划进行优化。
-
执行器:执行器是调用底层的运行框架执行逻辑执行计划。
Hive的底层存储:Hive的数据是存储在HDFS上的。Hive中的库和表可以看作是对HDFS上数据做的一个映射。所以Hive必须是运行在一个Hadoop集群上的。
Hive语句的执行过程:Hive中的执行器,是将最终要执行的MapReduce程序放到YARN上以一系列Job的方式去执行。
Hive的元数据存储:Hive的元数据是一般是存储在MySQL这种关系型数据库上的,Hive和MySQL之间通过MetaStore服务交互。
Hive客户端:Hive有很多种客户端。
-
cli命令行客户端:采用交互窗口,用hive命令行和Hive进行通信。
-
HiveServer2客户端:用Thrift协议进行通信,Thrift是不同语言之间的转换器,是连接不同语言程序间的协议,通过JDBC或者ODBC去访问Hive。
-
HWI客户端:hive自带的一个客户端,但是比较粗糙,一般不用。
-
HUE客户端:通过Web页面来和Hive进行交互,使用的比较多。
三.基本数据类型
Hive支持关系型数据中大多数基本数据类型,同时Hive中也有特有的三种复杂类型。下面的表列出了Hive中的常用基本数据类型:
数据类型 | 长度 | 备注 |
---|---|---|
Tinyint | 1字节的有符号整数 | -128~127 |
SmallInt | 1个字节的有符号整数 | -32768~32767 |
Int | 4个字节的有符号整数 | -2147483648 ~ 2147483647 |
BigInt | 8个字节的有符号整数 | |
Boolean | 布尔类型,true或者false | true、false |
Float | 单精度浮点数 | |
Double | 双精度浮点数 | |
String | 字符串 | |
TimeStamp | 整数 | 支持Unix timestamp,可以达到纳秒精度 |
Binary | 字节数组 | |
Date | 日期 | 0000-01-01 ~ 9999-12-31,常用String代替 |
--- | --- | --- |
四、DDL语法
4.1.创建数据库
创建一个数据库会在HDFS上创建一个目录,Hive里数据库的概念类似于程序中的命名空间,用数据库来组织表,在大量Hive的情况下,用数据库来分开可以避免表名冲突。Hive默认的数据库是default。创建数据库例子:
create database if not exists mydb2;
在hdfs目录下会建立该数据库。
4.2.查看数据库的定义
describe database mydb2;
注:通过数据库管理和开发工具dbeaver链接hive,运行该sql程序的输出。
hdfs://localhost/user/hive/warehouse/mydb2.db 是mydb2库对应的存储数据的HDFS上的根目录。
4.3.查看数据库列表
show databases;
4.4.删除数据库
drop database if exists mydb2 cacade;
删除数据库时,如果库中存在数据表,是不能删除的,要先删除所有表,再删除数据库。添加上cascade后,就可以先自动删除所有表后,再删除数据库。(友情提示:慎用啊!)删除数据库后,HDFS上数据库对应的目录就被删除掉了。
4.5.切换当前数据库
use mydb1;
4.6.创建普通表
创建表一般有几种方式:
-
create table 方式:
-----在该数据库下创建表格
use mydb2;
create table if not exists userinfo(
userid int,
username string,
cityid int,
createtime date
)
-----指定行之间的分隔符
row format delimited fields terminated by '\t'
-----文件存储格式
stored as textfile;
-
create table new_table as select 方式:根据查询的结果自动创建表,并将查询结果数据插入新建的表中。
-----此方式不能将原表中的default value也一同迁移过来,慎用。
create table new_userinfo as select * from table old_userinfo;
-----Using the CREATE TABLE ... AS SELECT ... command: This command will copy acrooss to the new table all the data,but the constraints triggers ,and so on will not be transferred to the new table.
-
create table like tablename方式:是克隆表,只复制tablename表的结构。复制表和克隆表会在下面的Hive数据管理部分详细讲解。
4.7.创建外部表
外部表是没有被hive完全控制的表,删除外部表时,HDFS中的数据文件不会一起被删除。而删除内部表时,表数据及HDFS中的数据文件都会被删除。
create external table if not exists external_userinfo(
userid int,
username string,
cityid int,
createtime date
)
-----指定行之间的分隔符
row format delimited fields terminated by '\t'
-----文件存储格式
stored as textfile;
-----同时删除外部表和内部表
drop table external_userinfo;
drop table userinfo;
4.8.创建分区
Hive查询一般是扫描整个目录,但是有时候我们关心的数据只是集中在某一部分数据上,比如我们一个Hive查询,往往是只是查询某一天的数据,这样的情况下,可以使用分区表来优化,一天是一个分区,查询时候,Hive只扫描指定天分区的数据。普通表和分区表的区别在于:一个Hive表在HDFS上是有一个对应的目录来存储数据,普通表的数据直接存储在这个目录下,而分区表数据存储时,是再划分子目录来存储的。一个分区一个子目录。主要作用是来优化查询性能。
4.9.创建分桶
桶表也是一种用于优化查询而设计的表类型。创建通表时,指定桶的个数、分桶的依据字段,hive就可以自动将数据分桶存储。查询时只需要遍历一个桶里的数据,或者遍历部分桶,这样就提高了查询效率。举例:
4.10.查看哪些表
show tables;
show tables '*info'; --正则表达
4.11.修改表
- 修改表名
alter table old_table rename to new_table;
- 添加字段
alter table user_info add columns (provinced int);
- 修改字段
alter table student change old_name new_name string;
修改字段,只是修改了Hive表的元数据信息(元数据信息一般是存储在MySql中),并不对存在于HDFS中的表数据做修改。并不是所有的Hive表都可以修改字段,只有使用了native SerDe (序列化反序列化类型)的表才能修改字段。
4.12.删除表
drop table if exists student;
五.DML语法
5.1.Hive中加载数据
- 加载到普通表
可以将本地文本文件内容批量加载到Hive表中,要求文本文件中的格式和Hive表的定义一致,包括:字段个数、字段顺序、列分隔符都要一致。这里的student表的表定义是以\t作为列分隔符,所以准备好数据后,将文本文件拷贝到hive客户端机器上后,执行加载命令。
create table if not exists student(
s_id string comment '学生id',
s_name string,
s_brith string,
s_sex string
)
row format delimited fields terminated by ','
stored as textfile;
load data local inpath '/home/luchenghui/data.txt' overwrite into table student;
-----local关键字表示源数据文件在本地,源文件可以在HDFS上,如果在HDFS上,则去掉local,inpath后面的路径是类似”hdfs://namenode:8020/user/datapath”这样的HDFS上文件的路径。
-----overwrite关键字表示如果hive表中存在数据,就会覆盖掉原有的数据。如果省略overwrite,则默认是追加数据。
加载完成数据后,在HDFS上就会看到加载的数据文件。
- 加载到分区表
create table if not exists student(
s_id string comment '学生id',
s_name string,
s_brith string,
s_sex string
)
partitioned by (dt string)
row format delimited fields terminated by ','
stored as textfile;
load data local inpath '/home/luchenghui/data.txt' overwrite into table student PARTITION (dt='2020-03-30');
--partition 是指定这批数据放入分区2020-03-30中。
加载完成数据后,在HDFS上就会看到加载的数据文件,多了一个dt=2020-03-30目录。
- 加载到分桶表中
create table if not exists student(
s_id string,
s_name string,
s_brith string,
s_sex string
)
row format delimited fields terminated by ','
stored as textfile;
-----分桶表
create table if not exists bucket_student(
s_id string,
s_name string,
s_brith string,
s_sex string
)
clustered by (s_sex) into 2 buckets
row format delimited fields terminated by ','
stored as textfile;
load data local inpath '/home/luchenghui/data.txt' overwrite into table student;
------导入分桶表
set hive.enforce.bucketing=true;
insert overwrite table bucket_student select * from student cluster by(s_sex);
- 导出数据
--导出数据,是将hive表中的数据导出到本地文件中。
insert overwrite local directory '/home/luchenghui/student_info.bak'
select * from bucket_student;
- 插入数据
-----insert select 语句
insert overwrite table insert_student select * from student;
-----这里是将查询结果导入到表中,overwrite关键字是覆盖目标表中的原来数据。如果缺省,就是追加数据。
-----一次遍历,多次插入分区表
from student
insert overwrite table log1 select companyid,originalstring where companyid='100006'
insert overwrite table log2 select companyid,originalstring where companyid='10002'
-----每次hive查询,都会将数据集整个遍历一遍。当查询结果会插入多个表中时,可以采用以上语法,将一次遍历写入多个表,以达到提高效率的目的。
- 克隆表
-----克隆表时会克隆源表的所有元数据信息,但是不会复制源表的数据。
create table like_student like student;
克隆的表无数据信息
- 备份表
-----备份是将表的元数据和数据都导出到HDFS上。
export table student to '/user/hive/warehouse/student.bak'
- 还原表
-----将备份在HDFS上的文件,还原到student表中,
import table student from '/user/hive/warehouse/student.bak';
- 复制表
-----复制表是将源表的结构和数据复制并创建为一个新表,复制过程中,可以对数据进行筛选,列可以进行删减。
create table copy_student as select * from student where s_sex='男';
六.HQL语法
6.1.Group By 分组
-----Hive不支持having语句,有对group by 后的结果进行筛选的需求,可以先将筛选条件放入group by的结果中,然后在包一层,在外边对条件进行筛选。
SELECT col1 FROM t1 GROUP BY col1 HAVING SUM(col2) > 10
可以用下面这种方式实现:
SELECT col1 FROM
(SELECT col1, SUM(col2) AS col2sum
FROM t1 GROUP BY col1
) t2
WHERE t2.col2sum > 10
6.2.字查询
-----Hive对子查询的支持有限,只允许在 select from 后面出现
SELECT * from (select s_sex,s_id,s_brith from student s where s.s_sex='男') a;
七.JOIN
Hive支持类似SQL Server的大部分Join操作,但是注意只支持等值连接,并不支持不等连接。原因是Hive语句最终是要转换为MapReduce程序来执行的,但是MapReduce程序很难实现这种不等判断的连接方式。
- Inner join
内连接同SQL Sever中的一样,连接的两个表中,只有同时满足连接条件的记录才会放入结果表中。
- Left join
同SQL Server中一样,两个表左连接时,符合Where条件的左侧表的记录都会被保留下来,而符合On条件的右侧的表的记录才会被保留下来。
- Right join
同Left Join相反,两个表左连接时,符合Where条件的右侧表的记录都会被保留下来,而符合On条件的左侧的表的记录才会被保留下来。
- Full join
Full Join会将连接的两个表中的记录都保留下来。
八.排序
8.1.Order By
select * from user_leads order by user_id
Hive中的Order By达到的效果和SQL Server中是一样的,会对查询结果进行全局排序,但是Hive语句最终要转换为MapReduce程序放到Hadoop分布式集群上去执行,Order By这样的操作,肯定要在Map后汇集到一个Reduce上执行,如果结果数据量大,那就会造成Reduce执行相当漫长。所以,Hive中尽量不要用Order By,除非非常确定结果集很小。但是排序的需求总是有的,Hive中使用下面的几种排序来满足需求。
8.2.Sort By
select * from user_leads sort by user_id
这个例子中,Sort By是在每个reduce中进行排序,是一个局部排序,可以保证每个Reduce中是按照user_id进行排好序的,但是全局上来说,相同的user_id可以被分配到不同的Reduce上,虽然在各个Reduce上是排好序的,但是全局上不一定是排好序的。
8.3.Distribute By 和 Sort By
--Distribute By 和Sort By实例
select * from user_leads where user_id!='0'
Distribute By cast(user_id as int) Sort by cast(user_id as int);
Distribute By 指定map输出结果怎么样划分后分配到各个Reduce上去,比如Distribute By user_id,就可以保证user_id字段相同的结果被分配到同一个reduce上去执行。然后再指定Sort By user_id,则在Reduce上进行按照user_id进行排序。但是这种还是不能做到全局排序,只能保证排序字段值相同的放在一起,并且在reduce上局部是排好序的。需要注意的是Distribute By 必须写在Sort By前面。
8.4.Cluster By
如果Distribute By和Sort By的字段是同一个,可以简写为 Cluster By.
select * from user_leads where user_id!='0'
Cluster By cast(user_id as int) ;
8.5.常见全局排序需求
常见的排序需求有两种:要求最终结果是有序的、按某个字段排序后取出前N条数据。
最终结果是有序的,最终分析结果往往是比较小的,因为客户不太可能最终要的是一个超级大数据集。所以实现方式是先得到一个小结果集,然后在得到最终的小结果集上使用order by 进行排序
select * from (
select user_id,count(leads_id) cnt from user_leads
where user_id!='0'
group by user_id
) a order by a.cnt;
这个语句让程序首先执行group by语句获取到一个小结果集,group by 过程中是不指定排序的,然后再对小结果集进行排序,这样得到的最终结果是全局排序的。
取前N条
select a.leads_id,a.user_name from (
select leads_id,user_name from user_leads
distribute by length(user_name) sort by length(user_name) desc limit 10
) a order by length(a.user_name) desc limit 10;
这个语句是查询user_name最长的10条记录,实现是先根据user_name的长度在各个Reduce上进行排序后取各自的前10个,然后再从10*N条的结果集里用order by取前10个。这个例子一定要结合MapReduce的执行原理和执行过程才能很好的理解,所以这个最能体现:看Hive语句要以MapReduce的角度看。
九、MapReduce执行过程
Hive语句最终是要转换为MapReduce程序放到Hadoop上去执行的,如果想深入了解Hive,并能够很好地优化Hive语句,了解MapReduce的执行过程至关重要,因为只有知道了MapReduce程序是怎么执行的,才能了解Hive语句是怎么执行的,才能有针对性地优化。
9.1.执行过程简介
MapReduce过程大体分为两个阶段:map函数阶段和reduce函数阶段,两个阶段之间有个shuffle。
-
Hadoop将MapReduce输入的数据划分为等长的小分片,一般每个分片是128M,因为HDFS的每个块是128M。Hadoop1.X中这个数是64M。
-
map函数是数据准备阶段,读取分片内容,并筛选掉不需要的数据,将数据解析为键值对的形式输出,map函数核心目的是形成对数据的索引,以供reduce函数方便对数据进行分析。
-
在map函数执行完后,进行map端的shuffle过程,map端的shuffle是将map函数的输出进行分区,不同分区的数据要传入不同的Reduce里去。
-
各个分区里的数据传入Reduce后,会先进行Reduce端的Shuffle过程,这里会将各个Map传递过来的相同分区的进行排序,然后进行分组,一个分组的数据执行一次reduce函数。
-
reduce函数以分组的数据为数据源,对数据进行相应的分析,输出结果为最终的目标数据。
-
由于map任务的输出结果传递给reduce任务过程中,是在节点间的传输,是占用带宽的,这样带宽就制约了程序执行过程的最大吞吐量,为了减少map和reduce间的数据传输,在map后面添加了combiner函数来就map结果进行预处理,combiner函数是运行在map所在节点的。
下面的示例图描述了整个MapReduce的执行过程:
工作机制
分片
HDFS上的文件要用很多mapper进程处理,而map函数接收的输入是键值对的形式,所以要先将文件进行切分并组织成键值对的形式,这个切分和转换的过程就是数据分片。在编写MapReduce程序时,可以通过job.setInputFormatClass()方法设置分片规则,如果没有指定默认是用TextInputFormat类。分片规则类都必须继承于FileInputFormat。
9.2.Map过程
每个数据分片将启动一个Map进程来处理,分片里的每个键值对运行一次map函数,根据map函数里定义的业务逻辑处理后,得到指定类型的键值对。
Map Shuffle过程
Map过程后要进行Map端的Shuffle阶段,Map端的Shuffle数据处理过程如下图所示:
环形缓冲区
Map输出结果是先放入内存中的一个环形缓冲区,这个环形缓冲区默认大小为100M(这个大小可以在io.sort.mb属性中设置),当环形缓冲区里的数据量达到阀值时(这个值可以在io.sort.spill.percent属性中设置)就会溢出写入到磁盘,环形缓冲区是遵循先进先出原则,Map输出一直不停地写入,一个后台进程不时地读取后写入磁盘,如果写入速度快于读取速度导致环形缓冲区里满了时,map输出会被阻塞直到写磁盘过程结束。
分区
从环形缓冲区溢出到磁盘过程,是将数据写入mapred.local.dir属性指定目录下的特定子目录的过程。 但是在真正写入磁盘之前,要进行一系列的操作,首先就是对于每个键,根据规则计算出来将来要输出到哪个reduce,根据reduce不同分不同的区,分区是在内存里分的,分区的个数和将来的reduce个数是一致的。
排序
在每个分区上,会根据键进行排序。
Combiner
combiner方法是对于map输出的结果按照业务逻辑预先进行处理,目的是对数据进行合并,减少map输出的数据量。排序后,如果指定了conmbiner方法,就运行combiner方法使得map的结果更紧凑,从而减少写入磁盘和将来网络传输的数据量。
合并溢出文件
环形缓冲区每次溢出,都会生成一个文件,所以在map任务全部完成之前,会进行合并成为一个溢出文件,每次溢出的各个文件都是按照分区进行排好序的,所以在合并文件过程中,也要进行分区和排序,最终形成一个已经分区和排好序的map输出文件。在合并文件时,如果文件个数大于某个指定的数量(可以在min.num.spills.for.combine属性设置),就会进再次combiner操作,如果文件太少,效果和效率上,就不值得花时间再去执行combiner来减少数据量了。
压缩
Map输出结果在进行了一系列的分区、排序、combiner合并、合并溢出文件后,得到一个map最终的结果后,就应该真正存储这个结果了,在存储之前,可以对最终结果数据进行压缩,一是可以节约磁盘空间,而是可以减少传递给reduce时的网络传输数据量。默认是不进行压缩的,可以在mapred.compress.map.output属性设置为true就启用了压缩,而压缩的算法有很多,可以在mapred.map.output.compression.codec属性中指定采用的压缩算法,具体压缩详情,可以看本文的后面部分的介绍。
9.3.Reduce Shuffle过程
Map端Shuffle完成后,将处理结果存入磁盘,然后通过网络传输到Reduce节点上,Reduce端首先对各个Map传递过来的数据进行Reduce 端的Shuffle操作,Reduce端的Shuffle过程如下所示:
复制数据
各个map完成时间肯定是不同的,只要有一个map执行完成,reduce就开始去从已完成的map节点上复制输出文件中属于它的分区中的数据,reduce端是多线程并行来复制各个map节点的输出文件的,线程数可以在mapred.reduce.parallel.copies属性中设置。
reduce将复制来的数据放入内存缓冲区(缓冲区大小可以在mapred.job.shuffle.input.buffer.percent属性中设置)。当内存缓冲区中数据达到阀值大小或者达到map输出阀值,就会溢写到磁盘。
写入磁盘之前,会对各个map节点来的数据进行合并排序,合并时如果指定了combiner,则会再次执行combiner以尽量减少写入磁盘的数据量。为了合并,如果map输出是压缩过的,要在内存中先解压缩后合并。
合并排序
合并排序其实是和复制文件同时并行执行的,最终目的是将来自各个map节点的数据合并并排序后,形成一个文件。
分组
分组是将相同key的键值对分为一组,一组是一个列表,列表中每一组在一次reduce方法中处理。
执行Reduce方法
Reduce端的Shuffle完成后,就交由reduce方法来进行处理了。
9.4.Reduce过程
Reduce端的Shuffle过程后,最终形成了分好组的键值对列表,相同键的数据分为一组,分组的键是分组的键,值是原来值得列表,然后每一个分组执行一次reduce函数,根据reduce函数里的业务逻辑处理后,生成指定格式的键值对。
十.性能优化
Hadoop启动开销大,如果每次只做小数量的输入输出,利用率将会很低。所以用好Hadoop的首要任务是增大每次任务所搭载的数据量。Hadoop的核心能力是parition和sort,因而这也是优化的根本。 Hive优化时,把hive Sql当做mapreduce程序来读,而不是当做SQL来读。
10.1.HiveQL层面优化
利用分区表优化
分区表是在某一个或者某几个维度上对数据进行分类存储,一个分区对应于一个目录。在这中的存储方式,当查询时,如果筛选条件里有分区字段,那么Hive只需要遍历对应分区目录下的文件即可,不用全局遍历数据,使得处理的数据量大大减少,提高查询效率。当一个Hive表的查询大多数情况下,会根据某一个字段进行筛选时,那么非常适合创建为分区表。
利用桶表优化
桶表的概念在前面有详细介绍,就是指定桶的个数后,存储数据时,根据某一个字段进行哈希后,确定存储在哪个桶里,这样做的目的和分区表类似,也是使得筛选时不用全局遍历所有的数据,只需要遍历所在桶就可以了。
join优化
-
优先过滤后再join,最大限度地减少参与Join的数据量。
-
小表join大表原则。 应该遵守小表join大表原则,原因是Join操作在reduce阶段,位于join左边的表内容会被加载进内存,将条目少的表放在左边,可以有效减少发生内存溢出的几率。join中执行顺序是从左到右生成Job,应该保证连续查询中的表的大小从左到右是依次增加的。
-
join on 条件相同的放入一个job. hive中,当多个表进行join时,如果join on的条件相同,那么他们会合并为一个MapReduce Job,所以利用这个特性,可以将相同的join on的放入一个job来节省执行时间。
启用mapjoin
mapjoin是将join双方比较小的表直接分发到各个map进程的内存中,在map进程中进行join操作,这样就省掉了reduce步骤,提高了速度。
-
hive.auto.convert.join
为true时,join方数据量小的表会整体分发到各个map进程的内存中,在map进程本地进行join操作,这样能大大提高运算效率,牺牲的是内存容量,所以数据量小于某一个值的才允许用mapjoin分发到各个map节点里,而这个值用以下参数来配置。
-
hive.auto.convert.join.noconditionaltask
设置为true,hive才基于输入文件大小进行自动转换为mapjoin.
-
hive.auto.convert.join.noconditionaltask.size
指定小于多少的表数据放入map内存,使用mapjoin,默认是10M.
-
这个优化只对join有效,对left join、right join 无效。
桶表mapjoin
当两个分桶表join时,如果join on的是分桶字段,小表的分桶数时大表的倍数时,可以启用map join来提高效率。启用桶表mapjoin要启用hive.optimize.bucketmapjoin参数。
Group By数据倾斜优化
Group By很容易导致数据倾斜问题,因为实际业务中,通常是数据集中在某些点上,这也符合常见的2/8原则,这样会造成对数据分组后,某一些分组上数据量非常大,而其他的分组上数据量很小,而在mapreduce程序中,同一个分组的数据会分配到同一个reduce操作上去,导致某一些reduce压力很大,其他的reduce压力很小,这就是数据倾斜,整个job执行时间取决于那个执行最慢的那个reduce。 解决这个问题的方法是配置一个参数:set hive.groupby.skewindata=true。
当选项设定为 true,生成的查询计划会有两个 MR Job。第一个 MR Job 中, Map的输出结果会随机分布到 Reduce 中,每个 Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的 Group By Key 有可能被分发到不同的 Reduce 中,从而达到负载均衡的目的,在第一个Job中通过聚合操作减少了数据量;第二个 MR Job 再根据预处理的数据结果按照 Group By Key 分布到 Reduce 中(这个过程可以保证相同的 GroupBy Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。
Order By 优化
因为order by只能是在一个reduce进程中进行的,所以如果对一个大数据集进行order by,会导致一个reduce进程中处理的数据相当大,造成查询执行超级缓慢。在要有进行order by 全局排序的需求时,用以下几个措施优化:
-
在最终结果上进行order by,不要在中间的大数据集上进行排序。如果最终结果较少,可以在一个reduce上进行排序时,那么就在最后的结果集上进行order by。
-
如果需求是取排序后前N条数据,那么可以使用distribute by和sort by在各个reduce上进行排序后取前N条,然后再对各个reduce的结果集合并后在一个reduce中全局排序,再取前N条,因为参与全局排序的Order By的数据量最多有reduce个数*N,所以速度很快。 例子:
Group By Map端聚合
并不是所有的聚合操作都需要在 Reduce 端完成,很多聚合操作都可以先在 Map端进行部分聚合,最后在 Reduce 端得出最终结果。hive.map.aggr = true 是否在 Map 端进行聚合,默认为 True。hive.groupby.mapaggr.checkinterval = 100000 在 Map 端进行聚合操作的条目数目
Join字段显示类型转换
当参与join的字段类型不一致时,Hive会自动进行类型转换,但是自动转换有时候效率并不高,可以根据实际情况通过显示类型转换来避免HIVE的自动转换。
使用orc、parquet等列式存储格式
创建表时,尽量使用orc、parquet这些列式存储格式,因为列式存储的表,每一列的数据在物理上是存储在一起的,Hive查询时会只遍历需要列数据,大大减少处理的数据量。
10.2.Hive架构层面优化
不执行MapReduce
Hive从HDFS读取数据,有两种方式:启用MapReduce读取、直接抓取。
很显然直接抓取数据比MapReduce读取数据要快的多,但是只有少数操作可以直接抓取数据,hive.fetch.task.conversion参数就是设置什么情况下采用直接抓取方法,它的值有两个:
-
minimal:只有 select * 、在分区字段上where过滤、有limit这三种场景下才启用直接抓取方式。
-
more:在select、where筛选、limit时,都启用直接抓取方式。
启用fetch more模式: set hive.fetch.task.conversion=more;
如果set hive.fetch.task.conversion=minimal,那么下面的查询语句会以MapReduce方法执行,运行时间比较长,但是改为more后,发现查询速度非常快。
本地模式执行MapReduce
Hive在集群上查询时,默认是在集群上N台机器上运行,需要多个机器进行协调运行,这个方式很好地解决了大数据量的查询问题。但是当Hive查询处理的数据量比较小时,其实没有必要启动分布式模式去执行,因为以分布式方式执行就涉及到跨网络传输、多节点协调等,并且消耗资源。这个时间可以只使用本地模式来执行mapreduce job,只在一台机器上执行,速度会很快。
启动本地模式涉及到三个参数:
参数名 | 默认值 | 备注 |
---|---|---|
hive.exec.mode.local.auto | false | 让hive决定是否在本地模式自动运行 |
hive.exec.mode.local.auto.input.files.max | 4 | 不启用本地模式的task最大个数 |
hive.exec.mode.local.auto.inputbytes.max | 128M | 不启动本地模式的最大输入文件大小 |
各个参数定义如下:
set hive.exec.mode.local.auto=true是打开hive自动判断是否启动本地模式的开关,但是只是打开这个参数并不能保证启动本地模式,要当map任务数不超过hive.exec.mode.local.auto.input.files.max的个数并且map输入文件大小不超过hive.exec.mode.local.auto.inputbytes.max所指定的大小时,才能启动本地模式。
JVM重用
因为Hive语句最终要转换为一系列的MapReduce Job的,而每一个MapReduce Job是由一系列的Map Task和Reduce Task组成的,默认情况下,MapReduce中一个Map Task或者一个Reduce Task就会启动一个JVM进程,一个Task执行完毕后,JVM进程就退出。这样如果任务花费时间很短,又要多次启动JVM的情况下,JVM的启动时间会变成一个比较大的消耗,这个时候,就可以通过重用JVM来解决。set mapred.job.reuse.jvm.num.tasks=5,这个设置就是制定一个jvm进程在运行多次任务之后再退出,这样一来,节约了很多的JVM的启动时间。
并行化
一个hive sql语句可能会转为多个mapreduce Job,每一个job就是一个stage,这些job顺序执行,这个在hue的运行日志中也可以看到。但是有时候这些任务之间并不是是相互依赖的,如果集群资源允许的话,可以让多个并不相互依赖stage并发执行,这样就节约了时间,提高了执行速度,但是如果集群资源匮乏时,启用并行化反倒是会导致各个job相互抢占资源而导致整体执行性能的下降。启用并行化:set hive.exec.parallel=true;
十一.后记
还是那句话,Hive入门使用很容易,这得益于它采用了类似SQL语句的方式与用户交互,这也是Hive被大量使用的原因,但是最好还是要理解Hive背后的执行原理,这样才能开发出高效的程序。 以上就是对Hive入门者的建议。
注:以上学习内容如果涉及版权内容,请联系本人进行删除。
参考资料
-
《Hive编程指南》 Eduard Capriolo、Dean Wampler、Jason Rutberglen (著),曹坤(译)。
-
Hive官方文档:https://cwiki.apache.org/confluence/display/Hive/GettingStarted.
-
互联网上其他资源。
-
参考学习来源:http://gitbook.cn/books/5924bd0523245b0aa3776b65/index.html