目錄 正文 1、什么是數(shù)據(jù)傾斜?
由于數(shù)據(jù)分布不均勻,造成數(shù)據(jù)大量的集中到一點(diǎn),造成數(shù)據(jù)熱點(diǎn)
2、Hadoop 框架的特性
A、不怕數(shù)據(jù)大,怕數(shù)據(jù)傾斜
B、Jobs 數(shù)比較多的作業(yè)運(yùn)行效率相對比較低,如子查詢比較多
C、 sum,count,max,min 等聚集函數(shù),通常不會有數(shù)據(jù)傾斜問題
3、主要表現(xiàn)
任務(wù)進(jìn)度長時間維持在 99%或者 100%的附近,查看任務(wù)監(jiān)控頁面,發(fā)現(xiàn)只有少量 reduce 子任務(wù)未完成,因?yàn)槠涮幚淼臄?shù)據(jù)量和其他的 reduce 差異過大。 單一 reduce 處理的記錄數(shù)和平均記錄數(shù)相差太大,通常達(dá)到好幾倍之多,最長時間遠(yuǎn)大 于平均時長。
4、容易數(shù)據(jù)傾斜情況

A、group by 不和聚集函數(shù)搭配使用的時候
B、count(distinct),在數(shù)據(jù)量大的情況下,容易數(shù)據(jù)傾斜,因?yàn)?count(distinct)是按 group by 字段分組,按 distinct 字段排序
C、 小表關(guān)聯(lián)超大表 join
5、產(chǎn)生數(shù)據(jù)傾斜的原因
A:key 分布不均勻
B:業(yè)務(wù)數(shù)據(jù)本身的特性
C:建表考慮不周全
D:某些 HQL 語句本身就存在數(shù)據(jù)傾斜
6、業(yè)務(wù)場景
(1)空值產(chǎn)生的數(shù)據(jù)傾斜
場景說明
在日志中,常會有信息丟失的問題,比如日志中的 user_id,如果取其中的 user_id 和用戶表中的 user_id 相關(guān)聯(lián),就會碰到數(shù)據(jù)傾斜的問題。
解決方案
解決方案 1:user_id 為空的不參與關(guān)聯(lián)
select * from log a join user b on a.user_id is not null and a.user_id = b.user_id
union all
select * from log c where c.user_id is null;
解決方案 2:賦予空值新的 key 值
select * from log a left outer join user b on
case when a.user_id is null then concat('hive',rand()) else a.user_id end = b.user_id
總結(jié)
方法 2 比方法 1 效率更好,不但 IO 少了,而且作業(yè)數(shù)也少了,方案 1 中,log 表 讀了兩次,jobs 肯定是 2,而方案 2 是 1。這個優(yōu)化適合無效 id(比如-99,’’,null)產(chǎn) 生的數(shù)據(jù)傾斜,把空值的 key 變
成一個字符串加上一個隨機(jī)數(shù),就能把造成數(shù)據(jù)傾斜的 數(shù)據(jù)分到不同的 reduce 上解決數(shù)據(jù)傾斜的問題。
改變之處:使本身為 null 的所有記錄不會擁擠在同一個 reduceTask 了,會由于有替代的 隨機(jī)字符串值,而分散到了多個 reduceTask 中了,由于 null 值關(guān)聯(lián)不上,處理后并不影響最終結(jié)果。
(2)不同數(shù)據(jù)類型關(guān)聯(lián)產(chǎn)生數(shù)據(jù)傾斜
場景說明
用戶表中 user_id 字段為 int,log 表中 user_id 為既有 string 也有 int 的類型, 當(dāng)按照兩個表的 user_id 進(jìn)行 join 操作的時候,默認(rèn)的 hash 操作會按照 int 類型的 id 進(jìn) 行分配,這樣就會導(dǎo)致所有的 string 類型的 id 就被分到同一個 reducer 當(dāng)中
解決方案
把數(shù)字類型 id 轉(zhuǎn)換成 string 類型的 id
select * from user a left outer join log b on b.user_id = cast(a.user_id as string)
(3)大小表關(guān)聯(lián)查詢產(chǎn)生數(shù)據(jù)傾斜
注意:使用map join解決小表關(guān)聯(lián)大表造成的數(shù)據(jù)傾斜問題。這個方法使用的頻率很高。
map join 概念:將其中做連接的小表(全量數(shù)據(jù))分發(fā)到所有 MapTask 端進(jìn)行 Join,從 而避免了 reduceTask,前提要求是內(nèi)存足以裝下該全量數(shù)據(jù)

以大表 a 和小表 b 為例,所有的 maptask 節(jié)點(diǎn)都裝載小表 b 的所有數(shù)據(jù),然后大表 a 的 一個數(shù)據(jù)塊數(shù)據(jù)比如說是 a1 去跟 b 全量數(shù)據(jù)做鏈接,就省去了 reduce 做匯總的過程。 所以相對來說,在內(nèi)存允許的條件下使用 map join 比直接使用 MapReduce 效率還高些, 當(dāng)然這只限于做 join 查詢的時候。
在 hive 中,直接提供了能夠在 HQL 語句指定該次查詢使用 map join,map join 的用法是 在查詢/子查詢的SELECT關(guān)鍵字后面添加/*+ MAPJOIN(tablelist) */提示優(yōu)化器轉(zhuǎn)化為map join(早期的 Hive 版本的優(yōu)化器是不能自動優(yōu)化 map join 的)。其中 tablelist 可以是一個 表,或以逗號連接的表的列表。tablelist 中的表將會讀入內(nèi)存,通常應(yīng)該是將小表寫在 這里。
MapJoin 具體用法:
select /* +mapjoin(a) */ a.id aid, name, age from a join b on a.id = b.id;
select /* +mapjoin(movies) */ a.title, b.rating from movies a join ratings b on a.movieid =
b.movieid;
在 hive0.11 版本以后會自動開啟 map join 優(yōu)化,由兩個參數(shù)控制:
set hive.auto.convert.join=true; //設(shè)置 MapJoin 優(yōu)化自動開啟
set hive.mapjoin.smalltable.filesize=25000000 //設(shè)置小表不超過多大時開啟 mapjoin 優(yōu)化
如果是大大表關(guān)聯(lián)呢?那就大事化小,小事化了。把大表切分成小表,然后分別 map join
那么如果小表不大不小,那該如何處理呢???
使用 map join 解決小表(記錄數(shù)少)關(guān)聯(lián)大表的數(shù)據(jù)傾斜問題,這個方法使用的頻率非常 高,但如果小表很大,大到 map join 會出現(xiàn) bug 或異常,這時就需要特別的處理
舉一例:日志表和用戶表做鏈接
select * from log a left outer join users b on a.user_id = b.user_id;
users 表有 600w+的記錄,把 users 分發(fā)到所有的 map 上也是個不小的開銷,而且 map join 不支持這么大的小表。如果用普通的 join,又會碰到數(shù)據(jù)傾斜的問題。
改進(jìn)方案:
select /*+mapjoin(x)*/* from log a
left outer join (
select /*+mapjoin(c)*/ d.*
from ( select distinct user_id from log ) c join users d on c.user_id = d.user_id
) x
on a.user_id = x.user_id;
假如,log 里 user_id 有上百萬個,這就又回到原來 map join 問題。所幸,每日的會員 uv 不會太多,有交易的會員不會太多,有點(diǎn)擊的會員不會太多,有傭金的會員不會太多等 等。所以這個方法能解決很多場景下的數(shù)據(jù)傾斜問題
|