Hive SQL执行原理图解

·  阅读 602

对于Hive的使用者来说,掌握Hive DDL和DML是最基本的操作,这在实际项目中是远远不够的。在实际项目实践中,经常会碰到诸如“这个Hive SQL怎么这么久了还执行不出来?明明数据量没有多大,怎么这个Hive SQL会发费这么多时间? 为什么我的Hive SQL一直hang在那里” 等问题

当然,这实际上涉及Hive SQL优化的问题,而要掌握Hive SQL优化问题,则必须理解其背后的执行原理和机制

在大数据时代,存储和计算和比较廉价了,运算能力更是非常强大的了,但是大数据也不是毫无成本的

一个好的Hive SQL和一个写的不好的Hive SQL对底层计算和资源的使用可能相差百倍甚至千倍、万倍,而一个计算集群的slot是有限的(尤其在实际项目中,你所在的部门被分配的slot更是有限的),一个使用者不恰当地使用Hive SQL可能会独占了整个集群的资源,导致其他使用者只能等待。如果是生产环境的任务,更有可能会带来生产的故障。

除了对资源的浪费之外,对于使用者而言,不恰当地使用Hive SQL有可能运行几个小甚至十几个小时都得不到运算结果,而如果恰当使用,则可能几分钟就会得到运算结果,因此掌握Hive SQL的使用对于工作效率也是非常大的提升。

鉴于此,本节将会重点介绍Hive SQL的执行过程和原理。在实际业务需求使用的Hive SQL可能千变万化,SQL逻辑也可能从简单的一行到几百上千行,但是其基本模式大致可以归为三类:

  • select语句:
select id,name from stu where id = 5 and name = '张三' 
复制代码

实际中 where条件可能更为复杂而且会有and/or等各种组合

  • group by语句:
select id,count(1) from stu group by id
复制代码
  • join语句:即连接两个表,实际中也可能连接多个表

实际Hive SQL开发中,只不过是将上述三种情况的组合,因此下面将分别针对上述三种情况介绍其后台执行机制和原理。

1、select语句执行图解

考虑如下的Hive SQL语句:

select order,byyer_id,cate_name from orders_table where day = '20170101' and cate_name = 'iphone7';
复制代码

其业务背景是希望分析苹果手机iphone7的客户情况,那么第一步就是必须将iphone7订单的明细抓取出来。此Hive SQL将2017年1月1日当天所有购买的商品时iphone7的所有订单明细抓取出来,以此作为分析的基础准备数据。实际抓取出来的记录可能数据量非常大,这里只是实例,可以加上limit语句(如 limit 1000)

那么,上述select语句在底层的Hadoop集群以及MapReduce上如何执行的呢?

其执行过程如图所示: Hive SQL是被翻译成MapReduce任务执行的,所以Hive SQL的执行阶段和MapReduce任务执行过程是一样的,也分为输入分片、Map阶段、Shuffle阶段和Reduce阶段等,下面结合上述过程大图逐一说明上述各个阶段

1、1 输入分片

实际项目设计中,订单表通常都会进行分区,一般按照自然天进行分区,所以一上述SQL 会限制day=20170101 实际上就限制了day=2020170101的区分文件(如果不限制分区条件,Hadoop会读取所有的订单文件,假如有2年730天的订单,那么就会读取这730天的所有订单文件,这就是上面所说的不恰当地Hive SQL,这会给Hadoop集群带来很多不必要的成本和开销),这样Hadoop就只会读取20170101的订单文件为300MB,分片(split)大小为128MB(Hadoop1.X是64MB,Hadoop2.X为128MB,此大小可通过参数配置),那么对应此任务split个数为3,其大小分别为:128MB,128MB,和44MB

1、2 Map阶段

Map任务的个数由分片阶段的split个数决定,上面SQL的输入文件(即20170101订单文件)被分成了三个输入分片,那么Hadoop集群就会启动3个Map任务(图简化期间,仅画出了两个Map Task)。每个Map任务接收自己的分片文件,并在Map函数中逐行对输入文件进行检查:商品类目是否为iphone7?不是的话,将其过滤,是的话,获取select 语句中指定的列值,并保存到本地文件中。

1、3 Shuffle和Reduce阶段

此SQL任务不涉及数据的重新分发和发布,不需要启动任何的Reduce任务

1、4 输出文件

Hadoop直接合并Map任务的输出文件到输出目录,对于本例,只需将各个Map任务的本地输出合并到输出目录即可。

Hive的select语句执行较为直接和简单,但是请理解其并行执行的概念。上述文件只有300MB,但是如果输入文件是3GB甚至是3TB呢?对于Hadoop来说,只需启动更多的Map任务即可。比如输入文件为3GB,那么需要启动(3 * 1024)MB/128MB = 24个Map任务,输入文件为3TB,需要启动(3 * 1024 * 1024)MB/128MB = 24576个Map任务,不考虑集群对Map任务数的限制,上述Select于具有的输入文件为300MB、3GB和3TB的执行时间通常在一个数量级,比如300MB的花费时间为1分钟,那么即使是3TB,执行时间也会在分钟级别。

这是因为所有的Map任务都是并行执行的,这也是Hadoop的优势所在,只需增加相应的机器和节点,就能处理更多的数据。对于实际用户来说,Hive SQL逻辑是一样的,无需做特殊处理,但是正如上述所说,请只使用所需的数据(通过制定分区条件),从而不对Hadoop集群的计算和存储资源带来不必要的开销。

2、group by 语句执行图解

数据分析中使用最为频繁的就是group by语句,下面详细介绍Hive的group by语句在Hadoop集群以及MapReduce上执行的方法。

select city,count(order_id) as iphone7_count from orders_table where day = '20170101' and cate_name = 'iphone7' group by city;
复制代码

上面的SQL,会对city进行分组,统计各城市的iphone订单数量,计算出每个城市的订单量,对其排序。自然很容易确定那个城市iphone7卖的最好、哪个城市卖的最差

上述Hive group by 语句的执行大图如图所示(注意,为了说明方便,下面大图将所有的cate_name都改成了iphone7): Hive SQL的group by语句涉及数据的重新分发和分布,因此其执行过程完整地包含了MapReduce任务的执行过程

2.1、输入分片

group by 语句的输入依然为day=20170101的分区文件,其输入分片过程和个数同select语句,也是被分为大小分别为:128MB,128MB,44MB的三个分片文件

2.2、Map阶段

Hadoop集群同样启动三个Map任务,处理对应的三个分片文件;每个map任务处理其对应分片文件中的每行,检查其商品类目是否为iphone7,如果是,则输出形如<city,1>的键值对,因此需要按照city对订单数目进行统计(注意和select语句的不同)

2.3 Combiner阶段

Combiner阶段是可选的,如果指定了Combiner操作,那么Hadoop会在Map任务的本地输出执行Combiner操作,其好处是可以去除冗余输出,避免不必要的后续处理和网络传输开销等。在次例中,MapTask1的输出中<hz,1>出现了两次,那么Combiner<hz,2>.但Combiner操作是有风险的,使用它的原则是Combiner的输出不会影响到Reduce计算的最终输入,例如,如果在计算只是求总数、最大值和最小值,可以使用Combiner,但是如果做平均值计算使用了Combiner,最终的Reduce计算结果就会出错。

2.4、Shuflle阶段

Map任务的输出必须经过一个名叫Shuffle的阶段才能交给Reducer处理。Shuffle过程是MapReduce的核心。指的是Map任务输出到Reduce任务输入的整个处理过程。完整地Shuffle过程包含了分区(partition)、排序(sort)和分割(spill)、复制(copy)、合并(merge)等过程,同时Shuffle又分为了Map端和Reduce端的Shuffle。

对于理解Hive SQL的group by语句来说,关键的过程实际就两个,即分区合并,所谓分区,即Hadoop如何决定将每个Map任务的,每个输出键值对分配到那个ReduceTask。所谓合并,即在一个ReduceTask中,如何将来自于多个MapTask的同样一个键的值进行合并。

以上述大图为例,MapTask1的输出包含了<hz,2>和<bj,1>两个键值对,那么MapTask1应该将上述键值对传递给ReduceTask1还是RediceTask2处理呢?Hadoop中最常用的分区方法是Hash Partitioner,即Hadoop会对每个键取Hash值,然后再对此Hash值按照reduce任务数目取模,从而得到对应的reducer,这样保证相同的键,肯定被分配到同一个reduce上,同时Hash函数也能确保Map任务的输出被均匀地分配到所有的Reduce任务上。

多个reduce任务的同样键值对会被Partition过程分配到同样的Reduce Task上,而merge操作即将它们的值进行合并,比如对于key = hz的键值对,MapTask1的输出为<hz,2>,MapTask2的输出为<hz,1>,Reduce Task1的merge过程会将其合并为<hz,{2,1}>,从而作为Reduce Task1的输入。

2.5、Reduce阶段

对于group by语句,Reduce Task接收形如<hz,{2,1}>的输入,Reduce Task只需要调用reduce函数逻辑将它们汇总即可,对于<hz,{2,1}>,即得到2+1=3.同样如果输入为<hz,{2,1,2,3}>,则得到2+1+2+3=8,每个reduce任务的输出存到本地文件中。

2.6、输出文件

Hadoop合并Reduce Task任务的输出文件到输出目录,对于本例,只需要将各两个Reduce Task的本地输出合并到输出目录即可。

3、join语句执行图解

除了group by语句,数据分析中还经常需要进行关联分析,也就是需要对两个表进行join操作,比如业务人员希望分析购买iphone7的客户年龄分布情况,订单表只包含了客户的ID,客户的年龄存在另一个表中,此时就需要对订单表和客户表进行join才能得到分析的结果,相关的SQL如下:

select t1.order_id,t1.buyer_id,t2.age
from
(
select order_id,buyer_id from orders_table where day = '20170101' and cate_name = 'iphone7'
) as t1
join
(
select buyer_id,age form buyer_table where buyer_staus = '有效'
) as t2
on t1.buyer_id = t2.buyer_id;
复制代码

上述的join SQL在Hadoop集群中会被拆分成三个MapReduce任务。

  • 第一个MapReduce任务:就是t1表部分,具体过程上就是1.1中的select 语句部分,不过此时的输出文件仅包含order_id,buyer_id列。
  • 第二个MapReduce任务:就是t2表部分,同样类似于1.1中的select语句,不过此时表变为了buyer表,输出列变为了buyer_id和age,同样过滤条件变为了buyer_staus = '有效'
  • 第三个MapReduce任务:即t1和t2表join过程,它将第一和第二的结果文件输出进行关联合并,然后输出。

上述Hive join语句的执行大图如下图所示(为了方便说明,第一个MapReduce任务和第二个MapReduce任务的执行过程大图不再展示,具体请参考上文中的selct语句执行大图,这里直接使用它们的输出文件)。

Hive SQL的join语句也涉及数据的重新分发和发布,但不同于group by的语句的是,group by语句会根据group by的列进行数据重分布和分发,而join语句则根据join的列进行数据的重分布和分发(再次即为根据buyer_id进行数据重分发和分布)。

3.1、输入分片

join语句的输入文件包括第一个MapReduce任务和第二个MapReduce任务的输出文件。对于Hadoop来说,依然会对它们根据文件大小进行分片,假如第一个输出文件的大小为150MB,那么会有两个分片:即128MB的分片和22MB的分片。第二个MapReduce任务的输出文件也会按照这样的方式划分分片。下面的大图为了简便起见,对每个输出文件都只画了一个分片输出。

3.2、Map阶段

Hadoop集群会对两个输出文件的split结果数据启动相应数目的MapTask,比如第一个输出文件包含了两个split,那么第一个输出文件回启东两个MapTask,第二个输出文件类似。

3.3、Shuffle阶段

对于join语句,Shuffle过程主要是Partition,根据其join的列进行数据的重分布和分发的过程。Join语句Partition列即为join的列,在此为buyer_id,因此对于输出文件1和2对应的所有Map任务,其输出将会根据buyer_id的值进行数据的重新分发和分布。 Join过程的key分发规格类似于group by语句,最常用的Partition方法也会Hash Partitioner,即会对每个join的键取hash值,然后对此hash的值按照Reduce任务数目取模,从而得到对应的Reducer,这样保证相同的join键,肯定被分配到同一个reducer上,从而完成列的关联

如图中假定,buyer_id为111和222的都会按照上述规则被分发到Reduce Task1,而buyer_id为333和444的都会被分发到Reduce Task2。

3.4、Reduce阶段

join语句的Reduce过程,是根据join键值将其他列关联进来的过程,比如对于buyer_id=222的列,MapTask1-1的order_id=1003,buyer_id=222的行会分发到Reduce Task1上,而Map Task2-1的buyer_id=222 age=25的列也会被分发到Reduce Task1上,此时Reduce Task1就根据buyer_id将它们的值关联合并成一行,并写入本地的输出文件中。

3.5、输入出文件

Hadoop合并Reduce Task任务的输出文件到输出目录,在本例中,只需要将每两个Reduce Task的本地输出合并到输出目录即可。

上述的join语句涉及两个表的join,那么如果是多表join呢?比如还有一个买家粒度为30的成交汇总表(比如包含其最近30天的成交量、成交金额等),加入SQL语句如下:

select t1.order_id,t1.buyer_id,t2.age,t3.buy_order_count_30d
from
(
select order_id,buyer_id from orders_table where day = '20170101' and cate_name = 'iphone7'
) as t1
join
(
select buyer_id,age form buyer_table where buyer_staus = '有效'
) as t2
on t1.buyer_id = t2.buyer_id
join
(
select buyer_id,buy_order_count_30d from buyer_stat_table
) as t3 
on a1.buyer_id = t3.buy_id 
复制代码

此SQL的执行过程和上面类似,只不过多了一个t3表,由于三个表都是通过buyer_id来join,因此三个表的行都会根据buyer_id在Shuffle过程进行分发,同一个buyer_id的三个表的行都会到一个Reduce Task上完成关联并输出结果。

那么如果t1表和t3表不是通过buyer_id关联,假如t3表是卖家表,join的SQL如下:

select t1.order_id,t1.buyer_id,t2.age,t3.seller_star_level
from
(
select order_id,buyer_id from orders_table where day = '20170101' and cate_name = 'iphone7'
) as t1
join
(
select buyer_id,age form buyer_table where buyer_staus = '有效'
) as t2
on t1.buyer_id = t2.buyer_id
join
(
select seller_id,seller_star_level from seller_table
) as t3
on a1.buyer_id = t3.seller_id 
复制代码

也就是说,t1订单表通过buyer_id和t2买家表关联,通过seller_id和t3卖家表关联,那么其执行过程又是如何的呢?

上述SQL实际上可以拆分成两次join:t1表和t2表通过buyer_id关联,其结果再和t3表通过seller_id关联吗,Hadoop实际上也就是这样处理的;这样实际上数据被Shuffle和Reduce了两次,第一次t1表t2表根据buyer_id被分发和关联(第一次Shuffle和Reduce),然后其结果再和t3表根据seller_id被分发和关联(第二次Shuffle和Reduce)。所以多表关联花费的时间肯定更长,因此其需要完成两次顺序的Reduce过程。

分类:
后端
标签: