[Hive]數(shù)據(jù)傾斜

在開(kāi)發(fā)或者面試過(guò)程中,如何解決hive的數(shù)據(jù)傾斜問(wèn)題是不可避免的。

發(fā)生數(shù)據(jù)傾斜的根本原因在于,shuffle之后,key的分布不均勻,使得大量key集中在某個(gè)reduce節(jié)點(diǎn),導(dǎo)致此節(jié)點(diǎn)處理的數(shù)據(jù)量過(guò)大。要解決此問(wèn)題,主要可以分為兩大塊:一是盡量不shuffle;二是shuffle之后,在reduce節(jié)點(diǎn)上的key分布盡量均勻。

具體來(lái)說(shuō),在實(shí)際開(kāi)發(fā)過(guò)程中,以下幾個(gè)業(yè)務(wù)過(guò)程可能會(huì)導(dǎo)致數(shù)據(jù)傾斜:
1.不可拆分的大文件引發(fā)的數(shù)據(jù)傾斜
2.業(yè)務(wù)無(wú)關(guān)的數(shù)據(jù)引發(fā)的數(shù)據(jù)傾斜
3.多維聚合計(jì)算數(shù)據(jù)膨脹引起的數(shù)據(jù)傾斜
4.無(wú)法削減中間結(jié)果的數(shù)據(jù)量引發(fā)的數(shù)據(jù)傾斜
5.兩個(gè)hive數(shù)據(jù)表連接時(shí)引發(fā)的數(shù)據(jù)傾斜

接下來(lái),我們對(duì)這五個(gè)問(wèn)題逐一分析。

1.不可拆分的大文件引發(fā)的數(shù)據(jù)傾斜
當(dāng)對(duì)文件使用GZIP壓縮等不支持文件分割操作的壓縮方式,在日后有作業(yè)涉及讀取壓縮后的文件時(shí),該壓縮文件只會(huì)被一個(gè)任務(wù)所讀取。如果該壓縮文件很大,則處理該文件的Map需要花費(fèi)的時(shí)間會(huì)遠(yuǎn)多于讀取普通文件的Map時(shí)間,該Map任務(wù)會(huì)成為作業(yè)運(yùn)行的瓶頸。這種情況也就是Map讀取文件的數(shù)據(jù)傾斜。

2.業(yè)務(wù)無(wú)關(guān)的數(shù)據(jù)引發(fā)的數(shù)據(jù)傾斜
實(shí)際業(yè)務(wù)中可能有大量的null值或者一些無(wú)意義的數(shù)據(jù)參與到計(jì)算作業(yè)中,這些數(shù)據(jù)可能來(lái)自業(yè)務(wù)為上報(bào)或者數(shù)據(jù)規(guī)范將某類(lèi)數(shù)據(jù)進(jìn)行歸一化變成空值或空字符串等形式,這些與業(yè)務(wù)無(wú)關(guān)的數(shù)據(jù)引入導(dǎo)致在分組聚合或者在執(zhí)行表連接時(shí)發(fā)生數(shù)據(jù)傾斜。
傾斜原因:

key相同的太集中,導(dǎo)致傾斜(很多和業(yè)務(wù)無(wú)關(guān)的null值)

解決方案:

對(duì)于group by操作,有兩種解決思路:1.直接對(duì)null值進(jìn)行過(guò)濾,2.對(duì)null值添加隨機(jī)前綴。
對(duì)于join操作,有兩種思路:1.手工分割,2.對(duì)null值添加隨機(jī)前綴。

對(duì)于上述解決方案,其實(shí)也可以與本文開(kāi)頭的兩類(lèi)思路對(duì)應(yīng)起來(lái),對(duì)null值進(jìn)行過(guò)濾和手工分割,這些都屬于盡量不shuffle。添加隨機(jī)前綴則屬于shuffle之后,使得在reduce節(jié)點(diǎn)上的key盡量分布均勻。

3.多維聚合計(jì)算數(shù)據(jù)膨脹引起的數(shù)據(jù)傾斜
在多維聚合計(jì)算時(shí),如果進(jìn)行分組聚合的字段較多,如下:

select a,b,c,count(1)from log group by a,b,c with rollup;

注:對(duì)于最后的with rollup關(guān)鍵字不知道大家用過(guò)沒(méi),with rollup是用來(lái)在分組統(tǒng)計(jì)數(shù)據(jù)的基礎(chǔ)上再進(jìn)行統(tǒng)計(jì)匯總,即用來(lái)得到group by的匯總信息。

如果上面的log表的數(shù)據(jù)量很大,并且Map端的聚合不能很好地起到數(shù)據(jù)壓縮的情況下,會(huì)導(dǎo)致Map端產(chǎn)出的數(shù)據(jù)急速膨脹,這種情況容易導(dǎo)致作業(yè)內(nèi)存溢出。如果log表含有數(shù)據(jù)傾斜key,會(huì)加劇Shuffle過(guò)程的數(shù)據(jù)傾斜。

注:對(duì)于最后的with rollup關(guān)鍵字拆分為如下幾個(gè)sql:

SELECT a, b, c, COUNT(1)
FROM log
GROUP BY a, b, c;

SELECT a, b, NULL, COUNT(1)
FROM log
GROUP BY a, b;

SELECT a, NULL, NULL, COUNT(1)
FROM log
GROUP BY a;

SELECT NULL, NULL, NULL, COUNT(1)
FROM log;

但是,上面這種方式不太友好,因?yàn)楝F(xiàn)在是對(duì)3個(gè)字段進(jìn)行分組聚合,如果是5個(gè)或者10個(gè),則需要拆解的SQL語(yǔ)句會(huì)更多。
在Hive中可以通過(guò)參數(shù) hive.new.job.grouping.set.cardinality 配置的方式自動(dòng)控制作業(yè)的拆解,該參數(shù)默認(rèn)值是30。表示針對(duì)grouping sets/rollups/cubes這類(lèi)多維聚合的操作,如果最后拆解的鍵組合大于該值,會(huì)啟用新的任務(wù)去處理大于該值之外的組合。如果在處理數(shù)據(jù)時(shí),某個(gè)分組聚合的列有較大的傾斜,可以適當(dāng)調(diào)小該值。

  1. 確實(shí)無(wú)法減少數(shù)據(jù)量引發(fā)的數(shù)據(jù)傾斜
    在一些操作中無(wú)法削減中間結(jié)果,例如使用collect_list聚合函數(shù),存在如下SQL:
select s_age,collect_list(s_score) list_score
from student
group by s_age

s_age有數(shù)據(jù)傾斜,但如果數(shù)據(jù)量大到一定的數(shù)量,會(huì)導(dǎo)致處理傾斜的Reduce任務(wù)產(chǎn)生內(nèi)存溢出的異常。

是否可以開(kāi)啟hive.groupby.skewindata參數(shù)來(lái)優(yōu)化。我們接下來(lái)分析下:

開(kāi)啟該配置會(huì)將作業(yè)拆解成兩個(gè)作業(yè),第一個(gè)作業(yè)會(huì)盡可能將Map的數(shù)據(jù)平均分配到Reduce階段,并在這個(gè)階段實(shí)現(xiàn)數(shù)據(jù)的預(yù)聚合,以減少第二個(gè)作業(yè)處理的數(shù)據(jù)量;第二個(gè)作業(yè)在第一個(gè)作業(yè)處理的數(shù)據(jù)基礎(chǔ)上進(jìn)行結(jié)果的聚合。

hive.groupby.skewindata的核心作用在于生成的第一個(gè)作業(yè)能夠有效減少數(shù)量。但是對(duì)于collect_list這類(lèi)要求全量操作所有數(shù)據(jù)的中間結(jié)果的函數(shù)來(lái)說(shuō),明顯起不到作用,反而因?yàn)橐胄碌淖鳂I(yè)增加了磁盤(pán)和網(wǎng)絡(luò)I/O的負(fù)擔(dān),而導(dǎo)致性能變得更為低下。
解決方案:

這類(lèi)問(wèn)題最直接的方式就是調(diào)整reduce所執(zhí)行的內(nèi)存大小。
調(diào)整reduce的內(nèi)存大小使用mapreduce.reduce.memory.mb這個(gè)配置。

5.表連接時(shí)引發(fā)的數(shù)據(jù)傾斜
兩表進(jìn)行join時(shí),如果表連接的鍵存在傾斜,那么在shuffle階段必然會(huì)引起數(shù)據(jù)傾斜。
解決方案:
5.1 MapJoin

通常做法是將傾斜的數(shù)據(jù)存到分布式緩存中,分發(fā)到各個(gè)Map任務(wù)所在節(jié)點(diǎn)。在map階段完成了join操作,即MapJoin,這避免了Shuffle,從而避免了數(shù)據(jù)傾斜。
set hive.auto.convert.join = true;
set hive.mapjoin.smalltable.filesize=25000000;

優(yōu)點(diǎn): 運(yùn)行時(shí)轉(zhuǎn)換為mapjoin,無(wú)reduce階段,運(yùn)行時(shí)間極短
缺點(diǎn): 適用場(chǎng)景有限,需要占用分布式內(nèi)存。由于是將小表加載進(jìn)內(nèi)存所以需要注意小表的大小。
5.2 手工分割
適用場(chǎng)景有限,因?yàn)閮A斜的key,除了key=A之外,還有其他key也可能會(huì)發(fā)生傾斜。
5.3大表添加N中隨機(jī)前綴,小表膨脹N倍數(shù)據(jù)
適用場(chǎng)景:小表不是很小,不太方便用mapjoin。

通常做法是對(duì)大表的key添加N中隨機(jī)前綴,對(duì)小表和N中隨機(jī)前綴做笛卡爾積,使得大表生成的key在小表里都有可能找到對(duì)應(yīng)的key,解決思路可參考如下代碼:

select a.*
from a
left join (
    select concat(c.rand_num,'_',d.key) as key from(
        select rand_num from dual LATERAL VIEW explode(array(0,1,2,3,4,5,6,7,8,9)) rand_num_list as rand_num
    )c join d
)b on concat(cast(9*rand() as int), '_', a.key) =b.key
where a.ds='2019-09-05' 

優(yōu)點(diǎn):可適當(dāng)降低傾斜程度
缺點(diǎn):N的取值不太好確定。而且數(shù)據(jù)膨脹后,會(huì)增加資源消耗

總結(jié)
以上就是面對(duì)hive數(shù)據(jù)傾斜時(shí),經(jīng)常采用的一些方法。我們?cè)谟龅綄?shí)際問(wèn)題時(shí),可以參考上述的方案,但是不需要完全照搬。因?yàn)樘幚頂?shù)據(jù)傾斜是一個(gè)綜合性的事情,考察的不僅是技術(shù)能力,更是對(duì)業(yè)務(wù)的熟悉程度。在處理數(shù)據(jù)傾斜問(wèn)題時(shí),我們首先要對(duì)表中的字段的意思有所了解。以上面的五種情況來(lái)說(shuō),對(duì)于null值,除了剔除以及添加隨機(jī)前綴,我們可以進(jìn)一步了解業(yè)務(wù),在源表中是否可能將null值進(jìn)行補(bǔ)全,或者在源表中是否可以給null值賦予一個(gè)隨機(jī)值。再比如進(jìn)行join操作時(shí),如果有數(shù)據(jù)傾斜,可以先看是否有數(shù)據(jù)發(fā)散的情況,如果有數(shù)據(jù)發(fā)散,可以考慮先處理數(shù)據(jù)發(fā)散的問(wèn)題??梢詫?duì)代碼進(jìn)行拆分,先保證數(shù)據(jù)不發(fā)散,再進(jìn)行join。
我們不管處理什么問(wèn)題,不管使用的工具是hive、spark或者python,首先要做到熟悉業(yè)務(wù),了解數(shù)據(jù)的含義。如果拋開(kāi)業(yè)務(wù),只是照搬書(shū)本上的所講的技術(shù),對(duì)代碼進(jìn)行調(diào)優(yōu),有時(shí)候也能解決問(wèn)題,但是對(duì)自己的提升較為片面。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容