时间飞梭而过,眼看这2018新年伊始,转眼间128大促已经落下帷幕,回顾过去的两周,协助大促监控和业务分析捞数竟然暂用了我大量的工作时间,期间不断的在用Hive SQL进行捞数分析,本着对工作认真负责的态度,对使用的语言做到知其然而知其所以然,最近好好的研究了一把Hive SQL的执行原理,以便写出性能更好的Hive SQL语句。
地球人都知道,我就不说了,直接跳过。。。
HIve SQL执行一般会经几个步骤:
背景:先来一个普通的Hive SQL的select语句,假设有个orders的订单明细表,我们希望获得iphone7的销售明细,那么Hive SQL只要一个简单的查询语句:
select * from orders where cate_name='iphone7';
其执行流程如下所示
1.1.1 输入分片
根据输入数据的大小进行分片
1.1.2 Map阶段
Map任务的个数有输入的分片结算的个数决定;在Map阶段对分片文件中的每行进行检查过滤,并按指定的列保存到bending文件。
1.1.3 Shuffle&Reduce阶段
由于此Hive SQL是一个简单的过滤语句,不需要启动Shuffle&Reduce阶段,所以直接跳过
1.1.4 输出文件
Hadoop直接合并Map任务的输出文件到输出目录。像以上这个例子,只需要简单的合并并输出的最终目录即可。
[注:截图来自书籍《离线和实时大数据开发实战》]
1.2 group by语句的执行原理
背景:如果需要继续统计每个城市的iphone7销售数目,就必须使用group by,Hive SQL可以如下所示:
select city, count(1) city_cnt from orders where cate_name='iphone7'
group by city;
[注:截图来自书籍《离线和实时大数据开发实战》]
Hive SQL group by的执行过程,相对于普通的select SQL操作,中间插入了几个环节:
1.2.1 Combine阶段
将Map阶段的输出文件进行一定程度的合并。
1.2.2 Shuffle阶段
Map任务的输出必须进过一个名叫Shuffle的阶段才能交给Reduce任务去处理。Shuffle过程是MapReduce的核心。包含了分区、排序、分隔、复制和合并等过程,而一般分区的算法主要使用hash的来完成。这里值得一提的是Shuffle包含了Map端的Shuffle和Reduce端的shuffle,接下来这个概念我们在优化的时候会用到。
1.2.3 Reduce阶段
对于group by句,这里需要调用reduce函数逻辑将数据按照group by的字段进行汇总,并保留文件到bending中。
1.3 join语句的执行原理
背景:假设还有一个是购买用户信息表,我们希望了解购买iphone7的年龄情况,那么我们就得用到join表,具体Hive SQL大概如下所示:
select t1.*, t2.*
(
select * from orders where cate_name='iphone7'
) t1
join
(
select * from buyer where 1=1
) t2
on t1.buyer_id=t2.buyer_id
1.3.1 Hive SQL join的执行过程其实可以分解为3个MapReduce
1.3.2 获得最终结果的MapReduce
最终的一次MapReduce是在t1&t2的基础上而来,根据关联的buyer_id进行Shuffle和Reduce操作,从而完成整个join的操作。此过程和group by类似,这里就不展开了。详细执行可以查看下图:
[注:截图来自书籍《离线和实时大数据开发实战》]
不过在说具体调优方法之前,我们先来看Hive SQL中调优的一个挑战:数据倾斜。什么是数据倾斜呢?所谓数据倾斜简单的理解就是在Hive SQL执行过程中的任何一个分片动作是如果数据分布式不均的情况。如果数据分布不均,那MapReduce分片并发处理的优势就无法体现,数据严重倾斜的分片就成为整个Hive SQL执行过程当中的关键路径;关键路径的性能低下就代表了整个Hive SQL执行效率的低下。所以基本上而已优化Hive SQL的思路就是在理解Hive SQL原理的情况下,尽量的避开数据倾斜从而提高并发,加快计算效率。
看过了执行原理之后,我们就可以对症下药说下如何调优了。而所谓的优化方法大致的可以分为几类:
背景:还是说回之前的订单明细表orders,假设我们需要通过供应商id进行group by来统计订单量。那这里就面临了一个问题,有的大供应商可能一天可以卖几百万单(夸张点哈哈哈),有些可能就可怜巴巴的就一两个订单,也就是说按照供应商id,势必会造成“数据倾斜”,那么整个Hive SQL的执行时间就有大供应商的执行时间所决定。
办法:针对这种case,比较好进行优化,只需要设置如下参数便可:
set hive.map.aggr=true
set hive.groupby.skewindata=true
count distinct的优化
背景:如果我要从订单明细表中统计有过少个供应商,那一般简单直接的办法是如下Hive SQL:
select count(distinct vendor_id) from orders
这个SQL由于distinct需要去重,在Map阶段之后会把说有分片数据分布到一个Reduce上面,这样如果数据量一多很容易造成问题,经过改良的办法应该如下所示,利用group by去重,再统计group by的行数:
select count(*) from
(
select vendor_id from orders group by vendor_id
) t
背景:继续上面的例子,假设每个供应商都有销售平台,比方说vip主站,MP,京东店铺等等,我们想要统计每个平台的订单数(假设有个表用来记录供应商对应的销售平台vendors),一般简单直接的SQL如下所示:
select b.platform, count(order_id) as order_cnt
from
(
select order_id, vendor_id from orders
) a
left outer join
(
select vendor_id, platform from vendors
) b
on a.verdor_id=b.verdor_id
group by b.platform
以上SQL按照vip销售的占比不同,极有可能造成数据倾斜。此种情况,如果在供应商数量不会太大的情况下,可以采用mapjoin语法进行优化,具体SQL如下所示:
select /\*+mapjoin(b)\*/
b.platform, count(order_id) as order_cnt
from
(
select order_id, vendor_id from orders
) a
left outer join
(
select vendor_id, platform from vendors
) b
on a.verdor_id=b.verdor_id
group by b.platform
上面的情况就是典型的大表关小表,orders明细是大表,而供应商信息表是典型的小表。此种方法加上mapjoin(b),是调整了执行过程,让Hive SQL在Map阶段进行job,而不是像上面所说在Reduce阶段才进行join列的操作。极端情况下,Hive能够将小表的数据copy到各个分片,直接进行lookup,速度更快。这样,在后期的Shufle&Reduce阶段就可以大大较少shuffle带来的IO网络开销,只是做一些简单的Redule,大大提高性能。
背景:还是同样orders,vendors表,我们现在要统计每个用户在各种平台的订单数,一般正常的sql应该如下:
select
a.user_id,
sum(1) as order_cnt
sum(case when b.platform='vip' then 1 end) as vip_order_cnt,
sum(case when b.platform='jingdong' then 1 end) as jingdong_order_cnt,
sum(case when b.platform='marketplace' then 1 end) as marketplace_order_cnt
from
(
select user_id, order_id, vendor_id from orders
) a
left outer join
(
select vendor_id, platform from vendors
) b
on a.verdor_id=b.verdor_id
group by user_id
同样的道理,大的供应商会造成数据倾斜;而假设现在的情况还复杂一点,供应商的数据表也有上千万的数据,那么数据都有可能超过了几个G,就无法沿用上面大表join小表的通过mapjoin的方式进行优化,那该这么办呢?没事接着往下走
一个正常优化的思路是,由于b表无法通过mapjoin优化,那我们就将b表优化成能够使用mapjoin的方式。优化的办法自然就是通过是用减少数据行或者数据列的方式实现。
比方说我们可以通过orders表中是否最近成过单的方式来过滤数据,SQL如下:
select /*+mapjoin(b)*/
a.user_id,
sum(1) as order_cnt
sum(case when b.platform='vip' then 1 end) as vip_order_cnt,
sum(case when b.platform='jingdong' then 1 end) as jingdong_order_cnt,
sum(case when b.platform='marketplace' then 1 end) as marketplace_order_cnt
from
(
select user_id, order_id, vendor_id from orders
) a
left outer join
(
select vendor_id, platform from vendors b0
join
(select verndor_id from verdors group by vendor_id) a0
on b0.vendor_id=a0.vendor_id
) b
on a.verdor_id=b.verdor_id
group by user_id
需要注意的是,以上方法能否奏效可能更数据分布有关,如果大大小小的供应商都成过单了,过滤效果就不好,这时我们就得通过另外一种通用方式了,各位客官,请接着看。。。
我们接着优化,如上文中提到,所谓的Hive SQL优化归根结底就是要消灭数据倾斜,这里提供了一种思路,如果我们针对数据倾斜的数据我们通过取模的方式将其数据平分到10,100,或者1000个分片中,那不就是相当于削峰操作了,那就消灭了数据不均的问题了。Hive SQL大致应该如下:
select
a.user_id,
sum(1) as order_cnt
sum(case when b.platform='vip' then 1 end) as vip_order_cnt,
sum(case when b.platform='jingdong' then 1 end) as jingdong_order_cnt,
sum(case when b.platform='marketplace' then 1 end) as marketplace_order_cnt
from
(
select user_id, order_id, vendor_id from orders
) a
left outer join
(
select /*+mapjoin(n0)*/
b0.vendor_id, b0.platform, n0.number from vendors b0
join tmp_numbers n0
) b
on a.verdor_id=b.verdor_id
and mod(a.order_id)=b.number
group by user_id
说明一下,这个tmp_numbers的表是一个临时表,只有10行记录,一个列number,对应的值依次为1,2,3,4…10.
和上面的微调优化方式有异曲同工之妙的优化方式,我们打供应商和小供应商区别对待,用完整的两种方式来处理,而最终将结果union all起来就是,大致Hive SQL如下:
select
user_id,
sum(1) as order_cnt
sum(case when platform='vip' then 1 end) as vip_order_cnt,
sum(case when platform='jingdong' then 1 end) as jingdong_order_cnt,
sum(case when platform='marketplace' then 1 end) as marketplace_order_cnt
from
(
select a.user_id, a.order_id, a.vendor_id, b.platform
(
select user_id, order_id, vendor_id from orders
) a
left outer join
(
select vendor_id, platform from vendors v0
left outer join
tmp_vendor_gt_1w w0
on v0.vendor_id=w0.vendor_id
where w0.vendor_id is null
) b
on a.verdor_id=b.verdor_id
union all
select /*+mapjoin(b)*/
a.user_id, a.order_id, a.vendor_id, b.platform
(
select user_id, order_id, vendor_id from orders
) a
left outer join
(
select vendor_id, platform from vendors v0
join
tmp_vendor_gt_1w w0
on v0.vendor_id=w0.vendor_id
) b
on a.verdor_id=b.verdor_id
)
group by user_id
结语,Hive SQL的优化就是发现数据倾斜,然后通过各种方式避免数据倾斜,高效的利用Hadoon的MapReduce并发特性,提高性能。
参考文章及书记如下:
作者:蔡恒旋