hive优化(二)

问题 6:使用 map join 解决数据倾斜的常景下小表关联大表的问题,但如果小表很大,
怎么解决。这个使用的频率非常高,但如果小表很大,大到 map join 会出现 bug 或异常,
这时就需要特别的处理。以下例子:

Select * from log a
Left outer join members b
On a.memberid = b.memberid.
Members 有 600w+的记录,把 members 分发到所有的 map 上也是个不小的开销,而且
map join 不支持这么大的小表。如果用普通的 join,又会碰到数据倾斜的问题。
解决方法:
Select /*+mapjoin(x)*/* from log a
Left outer join (select /*+mapjoin(c)*/d.*
From (select distinct memberid from log ) c
Join members d
On c.memberid = d.memberid
)x
On a.memberid = b.memberid。
先根据 log 取所有的 memberid,然后 mapjoin 关联 members 取今天有日志的 members
的信息,然后在和 log 做 mapjoin。
假如, log 里 memberid 有上百万个,这就又回到原来 map join 问题。所幸,每日的会员
uv 不会太多,有交易的会员不会太多,有点击的会员不会太多,有佣金的会员不会太多等
等。所以这个方法能解决很多场景下的数据倾斜问题。

问题 7: HIVE 下通用的数据倾斜解决方法,double 被关联的相对较小的表,这个方法在
mr 的程序里常用。 还是刚才的那个问题:

Select * from log a
Left outer join (select /*+mapjoin(e)*/memberid, number
From members d
Join num e
) b
On a.memberid= b.memberid
And mod(a.pvtime,30)+1=b.number。
Num 表只有一列 number,有 30 行,是 1,30 的自然数序列。就是把 member 表膨胀成
30 份,然后把 log 数据根据 memberid 和 pvtime 分到不同的 reduce 里去,这样可以保证
每个 reduce 分配到的数据可以相对均匀。就目前测试来看,使用 mapjoin 的方案性能稍
好。后面的方案适合在 map join 无法解决问题的情况下。

如下的优化方案可以做成通用的 hive 优化方法

1. 采样 log 表,哪些 memberid 比较倾斜,得到一个结果表 tmp1。由于对计算框架来
说,所有的数据过来,他都是不知道数据分布情况的,所以采样是并不可少的。 Stage1

2. 数据的分布符合社会学统计规则,贫富不均。倾斜的 key 不会太多,就像一个社会的富
人不多,奇特的人不多一样。所以 tmp1 记录数会很少。把 tmp1 和 members 做 map

3. map 读入 members 和 log,假如记录来自 log,则检查 memberid 是否在 tmp2 里,
如果是,输出到本地文件 a,否则生成<memberid,value>的 key,value 对,假如记录来自
member,生成<memberid,value>的 key,value 对,进入 reduce 阶段。 Stage3.

4. 最终把 a 文件,把 Stage3 reduce 阶段输出的文件合并起写到 hdfs。
这个方法在 hadoop 里应该是能实现的。 Stage2 是一个 map 过程,可以和 stage3 的
map 过程可以合并成一个 map 过程。
这个方案目标就是:倾斜的数据用 mapjoin,不倾斜的数据用普通的 join,最终合并得到完
整的结果。用 hive sql 写的话, sql 会变得很多段,而且 log 表会有多次读。倾斜的 key始终是很少的,这个在绝大部分的业务背景下适用。那是否可以作为 hive 针对数据倾斜join 时候的通用算法呢?

问题 8:多粒度(平级的)uv 的计算优化,比如要计算店铺的 uv。还有要计算页面的
uv,pvip

方案 1:
Select shopid,count(distinct uid)
From log group by shopid;
Select pageid, count(distinct uid),
From log group by pageid;
由于存在数据倾斜问题,这个结果的运行时间是非常长的。

方案二:
From log
Insert overwrite table t1 (type=’1’)
Select shopid
Group by shopid ,acookie
Insert overwrite table t1 (type=’2’)
Group by pageid,acookie;
店铺 uv:
Select shopid,sum(1)
From t1
Where type =’1’Group by shopid ;
页面 uv:
Select pageid,sum(1)
From t1
Where type =’1’
Group by pageid ;
这里使用了 multi insert 的方法,有效减少了 hdfs 读,但 multi insert 会增加 hdfs 写,
多一次额外的 map 阶段的 hdfs 写。使用这个方法,可以顺利的产出结果。

方案三:
Insert into t1
Select type,type_name,’’ as uid
From (
Select ‘page’ as type,
Pageid as type_name,
Uid
From log
Union all
Select ‘shop’ as type,
Shopid as type_name,
Uid
From log ) yGroup by type,type_name,uid;
Insert into t2
Select type,type_name,sum(1)
From t1
Group by type,type_name;
From t2
Insert into t3
Select type,type_name,uv
Where type=’page’
Select type,type_name,uv
Where type=’shop’ ;
最终得到两个结果表 t3,页面 uv 表, t4,店铺结果表。从 io 上来说, log 一次读。但比方案
2 少次 hdfs 写(multi insert 有时会增加额外的 map 阶段 hdfs 写)。作业数减少 1 个到
3,有 reduce 的作业数由 4 减少到 2,第三步是一个小表的 map 过程,分下表,计算资源
消耗少。但方案 2 每个都是大规模的去重汇总计算。
这个优化的主要思路是, map reduce 作业初始化话的时间是比较长,既然起来了,让他
多干点活,顺便把页面按 uid 去重的活也干了,省下 log 的一次读和作业的初始化时间,
省下网络 shuffle 的 io,但增加了本地磁盘读写。效率提升较多。
这个方案适合平级的不需要逐级向上汇总的多粒度 uv 计算,粒度越多,节省资源越多,比较通用。

问题 9:多粒度,逐层向上汇总的 uv 结算。 比如 4 个维度, a,b,c,d,分别计算
a,b,c,d,uv;a,b,c,uv;a,b,uv;a;uv,total uv4 个结果表。这可以用问题 8 的方案二,这里由于 uv 场景的
特殊性,多粒度,逐层向上汇总,就可以使用一次排序,所有 uv 计算受益的计算方法。

案例: 目前 mm_log 日志一天有 25 亿+的 pv 数,要从 mm 日志中计算 uv,与 ipuv,一共
计算
三个粒度的结果表
(memberid,siteid,adzoneid,province,uv,ipuv) R_TABLE_4
(memberid,siteid,adzoneid,uv,ipuv) R_TABLE_3
(memberid,siteid,uv,ipuv) R_TABLE_2
第一步:按 memberid,siteid,adzoneid,province,使用 group 去重,产生临时表,对
cookie,ip
打上标签放一起,一起去重,临时表叫 T_4;
Select memberid,siteid,adzoneid,province,type,user
From(
Select memberid,siteid,adzoneid,province,‘a’ type ,cookie as user from mm_log where
ds=20101205
Union all
Select memberid,siteid,adzoneid,province,‘i’ type ,ip as user from mm_log where
ds=20101205
) x group by memberid,siteid,adzoneid,province,type,user ;
第二步:排名,产生表 T_4_NUM.Hadoop 最强大和核心能力就是 parition 和 sort.按 type,
acookie 分组,
Type, acookie, memberid,siteid,adzoneid,province 排名。
Select * ,row_number(type,user,memberid,siteid,adzoneid ) as adzone_num ,
row_number(type,user,memberid,siteid ) as site_num,
row_number(type,user,memberid ) as member_num,
row_number(type,user ) as total_num
from (select * from T_4 distribute by type,user sort by type,user,
memberid,siteid,adzoneid ) x;
这样就可以得到不同层次粒度上 user 的排名,相同的 user id 在不同的粒度层次上,排名
等于 1 的记录只有 1 条。取排名等于 1 的做 sum,效果相当于 Group by user 去重后做
sum 操作。
第三步:不同粒度 uv 统计,先从最细粒度的开始统计,产生结果表 R_TABLE_4,这时,
结果集只有 10w 的级别。
如统计 memberid,siteid,adzoneid,provinceid 粒度的 uv 使用的方法就是
Select memberid,siteid,adzoneid, provinceid,
sum(case when type =’a’ then cast(1) as bigint end ) as province_uv ,
sum(case when type =’i’ then cast(1) as bigint end ) as province_ip ,
sum(case when adzone_num =1 and type =’a’ then cast(1) as bigint end ) as
adzone_uv ,
sum(case when adzone_num =1 and type =’i’ then cast(1) as bigint end ) as adzone_ip ,
sum(case when site_num =1 and type =’a’ then cast(1) as bigint end ) as site_uv ,
sum(case when site_num =1 and type =’i’ then cast(1) as bigint end ) as site_ip ,
sum(case when member_num =1 and type =’a’ then cast(1) as bigint end ) as
member_uv ,
sum(case when member_num =1 and type =’i’ then cast(1) as bigint end ) as
member_ip ,sum(case when total_num =1 and type =’a’ then cast(1) as bigint end ) as total_uv ,
sum(case when total_num =1 and type =’i’ then cast(1) as bigint end ) as total_ip ,
from T_4_NUM
group by memberid,siteid,adzoneid, provinceid ;
广告位粒度的 uv 的话,从 R_TABLE_4 统计,这是源表做 10w 级别的统计
Select memberid,siteid,adzoneid,sum(adzone_uv),sum(adzone_ip)
From R_TABLE_4
Group by memberid,siteid,adzoneid;
memberid,siteid 的 uv 计算 ,
memberid 的 uv 计算,
total uv 的计算也都从 R_TABLE_4 汇总
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章