在做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表現:
任務進度長時間維持在99%(或100%),查看任務監控頁面,發現只有少量(1個或幾個)reduce子任務未完成。因為其處理的資料量和其他reduce差異過大。
單一reduce的記錄數與平均記錄數差異過大,通常可能達到3倍甚至更多。 最長時間長度遠大於平均時間長度。
2資料扭曲的解決方案
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典型的業務情境
3.1空值產生的資料扭曲
情境:如日誌中,常會有資訊丟失的問題,比如日誌中的 user_id,如果取其中的 user_id 和 使用者表中的user_id 關聯,會碰到資料扭曲的問題。
解決方案1: user_id為空白的不參與關聯(紅色字型為修改後)
select * from log a join users b on a.user_id is not null and a.user_id = b.user_idunion allselect * from log a where a.user_id is null;
解決方案2 :賦與空值分新的key值
select * from log a left outer join users b on case when a.user_id is null then concat(‘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上 ,解決資料扭曲問題。
3.2不同資料類型關聯產生資料扭曲
情境:使用者表中user_id欄位為int,log表中user_id欄位既有string類型也有int類型。當按照user_id進行兩個表的Join操作時,預設的Hash操作會按int型的id來進行分配,這樣會導致所有string類型id的記錄都分配到一個Reducer中。
解決方案:把數字類型轉換成字串類型
select * from users a left outer join logs b on a.usr_id = cast(b.user_id as string)
3.3小表不小不大,怎麼用 map join 解決傾斜問題
使用 map join 解決小表(記錄數少)關聯大表的資料扭曲問題,這個方法使用的頻率非常高,但如果小表很大,大到map join會出現bug或異常,這時就需要特別的處理。 以下例子:
select * from log a left outer join users b on a.user_id = b.user_id;
users 表有 600w+ 的記錄,把 users 分發到所有的 map 上也是個不小的開銷,而且 map join 不支援這麼大的小表。如果用普通的 join,又會碰到資料扭曲的問題。
解決方案:
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 = b.user_id;
假如,log裡user_id有上百萬個,這就又回到原來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語句調節進行最佳化
轉自 http://www.tbdata.org/archives/2109