優化時,把hive sql當做map reduce程序來讀,會有意想不到的驚喜。(當然我們可以在寫hive語句的時候,可以看看hive是怎麼把sql語句編程mapreduce的)
理解hadoop的核心能力,是hive優化的根本。這是這一年來,項目組所有成員寶貴的經驗總結。
在做Shuffle階段的優化過程中,遇到了數據傾斜的問題,造成了對一些情況下優化效果不明顯。主要是因爲在Job完成後的所得到的Counters是整個Job的總和,優化是基於這些Counters得出的平均值,而由於數據傾斜的原因造成map處理數據量的差異過大,使得這些平均值能代表的價值降低。Hive的執行是分階段的,map處理數據量的差異取決於上一個stage的reduce輸出,所以如何將數據均勻的分配到各個reduce中,就是解決數據傾斜的根本所在。規避錯誤來更好的運行比解決錯誤更高效。在查看了一些資料後,總結如下。
1數據傾斜的原因
1.1操作:
關鍵詞 |
情形 |
後果 |
Join |
其中一個表較小, 但是key集中 |
分發到某一個或幾個Reduce上的數據遠高於平均值 |
大表與大表,但是分桶的判斷字段0值或空值過多 |
這些空值都由一個reduce處理,灰常慢 |
|
group by |
group by 維度過小, 某值的數量過多 |
處理某值的reduce灰常耗時 |
Count Distinct |
某特殊值過多 |
處理此特殊值的reduce耗時 |
1.2原因:
1)、key分佈不均勻
2)、業務數據本身的特性
3)、建表時考慮不周
4)、某些SQL語句本身就有數據傾斜
1.3表現:
1.不怕數據多,就怕數據傾斜。
2.任務進度長時間維持在99%(或100%),查看任務監控頁面,發現只有少量(1個或幾個)reduce子任務未完成。因爲其處理的數據量和其他reduce差異過大。
單一reduce的記錄數與平均記錄數差異過大,通常可能達到3倍甚至更多。 最長時長遠大於平均時長。
3.對sum,count來說,不存在數據傾斜問題。
4.對count(distinct ),效率較低,數據量一多,準出問題,如果是多count(distinct )效率更低。
2數據傾斜的解決方案
優化可以從幾個方面着手:
1. 好的模型設計事半功倍。
2. 解決數據傾斜問題。
3. 減少job數。
4. 設置合理的map reduce的task數,能有效提升性能。(比如,10w+級別的計算,用160個reduce,那是相當的浪費,1個足夠)。
5. 自己動手寫sql解決數據傾斜問題是個不錯的選擇。set hive.groupby.skewindata=true;這是通用的算法優化,但算法優化總是漠視業務,習慣性提供通用的解決方法。 Etl開發人員更瞭解業務,更瞭解數據,所以通過業務邏輯解決傾斜的方法往往更精確,更有效。
6. 對count(distinct)採取漠視的方法(就是說不要用了),尤其數據大的時候很容易產生傾斜問題,不抱僥倖心理。自己動手,豐衣足食。
7. 對小文件進行合併,是行至有效的提高調度效率的方法,假如我們的作業設置合理的文件數,對雲梯的整體調度效率也會產生積極的影響。
8. 優化時把握整體,單個作業最優不如整體最優。
2.1參數調節:
hive.map.aggr=true
Map 端部分聚合,相當於Combiner
hive.groupby.skewindata=true
有數據傾斜的時候進行負載均衡,當選項設定爲 true,生成的查詢計劃會有兩個 MR Job。第一個 MR Job 中,Map 的輸出結果集合會隨機分佈到 Reduce 中,每個 Reduce 做部分聚合操作,並輸出結果,這樣處理的結果是相同的 Group By Key 有可能被分發到不同的 Reduce 中,從而達到負載均衡的目的;第二個 MR Job 再根據預處理的數據結果按照 Group By Key 分佈到 Reduce 中(這個過程可以保證相同的 Group By Key 被分佈到同一個 Reduce 中),最後完成最終的聚合操作。
2.2 SQL語句調節:
如何Join:
關於驅動表的選取,選用join key分佈最均勻的表作爲驅動表
做好列裁剪和filter操作,以達到兩表做join的時候,數據量相對變小的效果。
大小表Join:
使用map join讓小的維度表(1000條以下的記錄條數) 先進內存。在map端完成reduce.
大表Join大表:
把空值的key變成一個字符串加上隨機數,把傾斜的數據分到不同的reduce上,由於null值關聯不上,處理後並不影響最終結果。
count distinct大量相同特殊值
count distinct時,將值爲空的情況單獨處理,如果是計算count distinct,可以不用處理,直接過濾,在最後結果中加1。如果還有其他計算,需要進行group by,可以先將值爲空的記錄單獨處理,再和其他計算結果進行union。
group by維度過小:
採用sum() group by的方式來替換count(distinct)完成計算。
特殊情況特殊處理:
在業務邏輯優化效果的不大情況下,有些時候是可以將傾斜的數據單獨拿出來處理。最後union回去。
3典型的業務場景
遷移和優化過程中的案例:
問題1:如日誌中,常會有信息丟失的問題,比如全網日誌中的user_id,如果取其中的user_id和bmw_users關聯,就會碰到數據傾斜的問題。(空值產生的數據傾斜)
方法:解決數據傾斜問題
解決方法1. User_id爲空的不參與關聯,例如:
Select *
From log a
Join bmw_users b
On a.user_id is not null
And a.user_id = b.user_id
Union all
Select *
from log a
where a.user_id is null.
這兒我不得不說明一下:
舉個小例子:
就是把2個具有相同列及數據類型的 結果 放到一起顯示,並且不去重。
select a,b,c from table1
union all
select ca,cb,cc from table2
解決方法2 :
Select *
from log a
left outer join bmw_users b
on case when a.user_id is null then concat(‘dp_hive’,rand() ) else a.user_id end = b.user_id;
總結:2比1效率更好,不但io少了,而且作業數也少了。1方法log讀取兩次,jobs是2。2方法job數是1 。這個優化適合無效id(比如-99,’’,null等)產生的傾斜問題。把空值的key變成一個字符串加上隨機數,就能把傾斜的數據分到不同的reduce上 ,解決數據傾斜問題。因爲空值不參與關聯,即使分到不同的reduce上,也不影響最終的結果。附上hadoop通用關聯的實現方法(關聯通過二次排序實現的,關聯的列爲parition key,關聯的列c1和表的tag組成排序的group key,根據parition key分配reduce。同一reduce內根據group key排序)。
問題2:不同數據類型id的關聯會產生數據傾斜問題。
一張表s8的日誌,每個商品一條記錄,要和商品表關聯。但關聯卻碰到傾斜的問題。s8的日誌中有字符串商品id,也有數字的商品id,類型是string的,但商品中的數字id是bigint的。猜測問題的原因是把s8的商品id轉成數字id做hash來分配reduce,所以字符串id的s8日誌,都到一個reduce上了,解決的方法驗證了這個猜測。
方法:把數字類型轉換成字符串類型
Select * from s8_log a
Left outer join r_auction_auctions b
On a.auction_id = cast(b.auction_id as string);
問題3:利用hive 對UNION ALL的優化的特性
hive對union all優化只侷限於非嵌套查詢。
比如以下的例子:
select * from
(select * from t1
Group by c1,c2,c3
Union all
Select * from t2
Group by c1,c2,c3) t3
Group by c1,c2,c3;
從業務邏輯上說,子查詢內的group by 怎麼都看顯得多餘(功能上的多餘,除非有count(distinct)),如果不是因爲hive bug或者性能上的考量(曾經出現如果不子查詢group by ,數據得不到正確的結果的hive bug)。所以這個hive按經驗轉換成
select * from
(select * from t1
Union all
Select * from t2
) t3
Group by c1,c2,c3;
經過測試,並未出現union all的hive bug,數據是一致的。mr的作業數有3減少到1。
t1相當於一個目錄,t2相當於一個目錄,那麼對map reduce程序來說,t1,t2可以做爲map reduce 作業的mutli inputs。那麼,這可以通過一個map reduce 來解決這個問題。Hadoop的計算框架,不怕數據多,就怕作業數多。
但如果換成是其他計算平臺如oracle,那就不一定了,因爲把大的輸入拆成兩個輸入,分別排序彙總後merge(假如兩個子排序是並行的話),是有可能性能更優的(比如希爾排序比冒泡排序的性能更優)。
問題4:比如推廣效果表要和商品表關聯,效果表中的auction id列既有商品id,也有數字id,和商品表關聯得到商品的信息。那麼以下的hive sql性能會比較好
Select * from effect a
Join (select auction_id as auction_id from auctions
Union all
Select auction_string_id as auction_id from auctions
) b
On a.auction_id = b.auction_id。
比分別過濾數字id,字符串id然後分別和商品表關聯性能要好。
這樣寫的好處,1個MR作業,商品表只讀取一次,推廣效果表只讀取一次。把這個sql換成MR代碼的話,map的時候,把a表的記錄打上標籤a,商品表記錄每讀取一條,打上標籤b,變成兩個<key ,value>對,<b,數字id>,<b,字符串id>。所以商品表的hdfs讀只會是一次。
問題5:先join生成臨時表,在union all還是寫嵌套查詢,這是個問題。比如以下例子:
Select *
From (select *
From t1
Uion all
select *
From t4
Union all
Select *
From t2
Join t3
On t2.id = t3.id
) x
Group by c1,c2;
這個會有4個jobs。假如先join生成臨時表的話t5,然後union all,會變成2個jobs。
Insert overwrite table t5
Select *
From t2
Join t3
On t2.id = t3.id
;
Select * from (t1 union all t4 union all t5) ;
hive在union all優化上可以做得更智能(把子查詢當做臨時表),這樣可以減少開發人員的負擔。出現這個問題的原因應該是union all目前的優化只侷限於非嵌套查詢。如果寫MR程序這一點也不是問題,就是multi inputs。
問題6:使用map join解決數據傾斜的常景下小表關聯大表的問題,但如果小表很大,怎麼解決。這個使用的頻率非常高,但如果小表很大,大到map join會出現bug或異常,這時就需要特別的處理。雲瑞和玉璣提供了非常給力的解決方案。(小表不小不大,怎麼用 map join 解決傾斜問題)以下例子:
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)*/*
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不會太多,有交易的會員不會太多,有點擊的會員不會太多,有佣金的會員不會太多等等。所以這個方法能解決很多場景下的數據傾斜問題。
評價:他解決問題的方法很聰明,先是獲取了今天日誌中的不重複的會員(這也是根據我們的業務需求的),這樣就相當於把我們的表縮小了,這樣我們把變得小的表就可以放到內存了。
4總結
使map的輸出數據更均勻的分佈到reduce中去,是我們的最終目標。由於Hash算法的侷限性,按key Hash會或多或少的造成數據傾斜。大量經驗表明數據傾斜的原因是人爲的建表疏忽或業務邏輯可以規避的。在此給出較爲通用的步驟:
1、採樣log表,哪些user_id比較傾斜,得到一個結果表tmp1。由於對計算框架來說,所有的數據過來,他都是不知道數據分佈情況的,所以採樣是並不可少的。
2、數據的分佈符合社會學統計規則,貧富不均。傾斜的key不會太多,就像一個社會的富人不多,奇特的人不多一樣。所以tmp1記錄數會很少。把tmp1和users做map join生成tmp2,把tmp2讀到distribute file cache。這是一個map過程。
3、map讀入users和log,假如記錄來自log,則檢查user_id是否在tmp2裏,如果是,輸出到本地文件a,否則生成<user_id,value>的key,value對,假如記錄來自member,生成<user_id,value>的key,value對,進入reduce階段。
4、最終把a文件,把Stage3 reduce階段輸出的文件合併起寫到hdfs。
如果確認業務需要這樣傾斜的邏輯,考慮以下的優化方案:
1、對於join,在判斷小表不大於1G的情況下,使用map join
2、對於group by或distinct,設定 hive.groupby.skewindata=true
3、儘量使用上述的SQL語句調節進行優化