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 彙總
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章