天天看點

實操 | Hive 資料傾斜問題定位排查及解決

多數介紹資料傾斜的文章都是以大篇幅的理論為主,并沒有給出具體的資料傾斜案例。當工作中遇到了傾斜問題,這些理論很難直接應用,導緻我們面對傾斜時還是不知所措。

今天我們不扯大篇理論,直接以例子來實踐,排查是否出現了資料傾斜,具體是哪段代碼導緻的傾斜,怎麼解決這段代碼的傾斜。

當執行過程中任務卡在 99%,大機率是出現了資料傾斜,但是通常我們的 SQL 很大,需要判斷出是哪段代碼導緻的傾斜,才能利于我們解決傾斜。通過下面這個非常簡單的例子來看下如何定位産生資料傾斜的代碼。

表結構描述

先來了解下這些表中我們需要用的字段及資料量:

表的字段非常多,此處僅列出我們需要的字段

第一張表:user_info (使用者資訊表,使用者粒度)

字段名 字段含義 字段描述
userkey 使用者 key 使用者辨別
idno 使用者的身份證号 使用者實名認證時擷取
phone 使用者的手機号 使用者注冊時的手機号
name 使用者的姓名 使用者的姓名

user_info 表的資料量:1.02 億,大小:13.9G,所占空間:41.7G(HDFS三副本)

第二張表:user_active (使用者活躍表,使用者粒度)

字段名 字段含義 字段描述
userkey 使用者 key 使用者沒有注冊會配置設定一個 key
user_active_at 使用者的最後活躍日期 從埋點日志表中擷取使用者的最後活躍日期

user_active 表的資料量:1.1 億

第三張表:user_intend(使用者意向表,此處隻取近六個月的資料,使用者粒度)

字段名 字段含義 字段描述
phone 使用者的手機号 有意向的使用者必須是手機号注冊的使用者
intend_commodity 使用者意向次數最多的商品 客戶對某件商品意向次數最多
intend_rank 使用者意向等級 使用者的購買意願等級,級數越高,意向越大

user_intend 表的資料量:800 萬

第四張表:user_order(使用者訂單表,此處隻取近六個月的訂單資料,使用者粒度)

字段名 字段含義 字段描述
idno 使用者的身份證号 下訂單的使用者都是實名認證的
order_num 使用者的訂單次數 使用者近六個月下單次數
order_amount 使用者的訂單總金額 使用者近六個月下單總金額

user_order 表的資料量:640 萬

1. 需求

需求非常簡單,就是将以上四張表關聯組成一張大寬表,大寬表中包含使用者的基本資訊,活躍情況,購買意向及此使用者下訂單情況。

2. 代碼

根據以上需求,我們以 user_info 表為基礎表,将其餘表關聯為一個寬表,代碼如下:

select
  a.userkey,
  a.idno,
  a.phone,
  a.name,
  b.user_active_at,
  c.intend_commodity,
  c.intend_rank,
  d.order_num,
  d.order_amount
from user_info a
left join user_active b on a.userkey = b.userkey
left join user_intend c on a.phone = c.phone
left join user_order d on a.idno = d.idno;      

執行上述語句,在執行到某個 job 時任務卡在 99%:

實操 | Hive 資料傾斜問題定位排查及解決

這時我們就應該考慮出現資料傾斜了。其實還有一種情況可能是資料傾斜,就是任務逾時被殺掉,Reduce 處理的資料量巨大,在做 full gc 的時候,stop the world。導緻響應逾時,超出預設的 600 秒,任務被殺掉。報錯資訊一般如下:

​AttemptID:attempt_1624419433039_1569885_r_000000 Timed outafter 600 secs Container killed by the ApplicationMaster. Container killed onrequest. Exit code is 143 Container exited with a non-zero exit code 143​

3. 傾斜問題排查

資料傾斜大多數都是大 key 問題導緻的。

如何判斷是大 key 導緻的問題,可以通過下面方法:

1. 通過時間判斷

如果某個 reduce 的時間比其他 reduce 時間長的多,如下圖,大部分 task 在 1 分鐘之内完成,隻有 r_000000 這個 task 執行 20 多分鐘了還沒完成。

實操 | Hive 資料傾斜問題定位排查及解決

注意:要排除兩種情況:

  1. 如果每個 reduce 執行時間差不多,都特别長,不一定是資料傾斜導緻的,可能是 reduce 設定過少導緻的。
  2. 有時候,某個 task 執行的節點可能有問題,導緻任務跑的特别慢。這個時候,mapreduce 的推測執行,會重新開機一個任務。如果新的任務在很短時間内能完成,通常則是由于 task 執行節點問題導緻的個别 task 慢。但是如果推測執行後的 task 執行任務也特别慢,那更說明該 task 可能會有傾斜問題。

2. 通過任務 Counter 判斷

Counter 會記錄整個 job 以及每個 task 的統計資訊。counter 的 url 一般類似:

​http://bd001:8088/proxy/application_1624419433039_1569885/mapreduce/singletaskcounter/task_1624419433039_1569885_r_000000/org.apache.hadoop.mapreduce.FileSystemCounter​

通過輸入記錄數,普通的 task counter 如下,輸入的記錄數是 13 億多:

實操 | Hive 資料傾斜問題定位排查及解決
實操 | Hive 資料傾斜問題定位排查及解決

而 task=000000 的 counter 如下,其輸入記錄數是 230 多億。是其他任務的 100 多倍:

實操 | Hive 資料傾斜問題定位排查及解決

4. 定位 SQL 代碼

1. 确定任務卡住的 stage

  • 通過 jobname 确定 stage:

    一般 Hive 預設的 jobname 名稱會帶上 stage 階段,如下通過 jobname 看到任務卡住的為 Stage-4:

    實操 | Hive 資料傾斜問題定位排查及解決
  • 如果 jobname 是自定義的,那可能沒法通過 jobname 判斷 stage。需要借助于任務日志:

    找到執行特别慢的那個 task,然後 Ctrl+F 搜尋 “CommonJoinOperator: JOIN struct” 。Hive 在 join 的時候,會把 join 的 key 列印到日志中。如下:

實操 | Hive 資料傾斜問題定位排查及解決

上圖中的關鍵資訊是:struct<_col0:string, _col1:string, _col3:string>

這時候,需要參考該 SQL 的執行計劃。通過參考執行計劃,可以斷定該階段為 Stage-4 階段:

實操 | Hive 資料傾斜問題定位排查及解決

2. 确定 SQL 執行代碼

确定了執行階段,即 stage。通過執行計劃,則可以判斷出是執行哪段代碼時出現了傾斜。還是從此圖,這個 stage 中進行連接配接操作的表别名是 d:

實操 | Hive 資料傾斜問題定位排查及解決

就可以推測出是在執行下面紅框中代碼時出現了資料傾斜,因為這行的表的别名是 d:

實操 | Hive 資料傾斜問題定位排查及解決

5. 解決傾斜

我們知道了哪段代碼引起的資料傾斜,就針對這段代碼檢視傾斜原因,看下這段代碼的表中資料是否有異常。

傾斜原因:

本文的示例資料中 user_info 和 user_order 通過身份證号關聯,檢查發現 user_info 表中身份證号為空的有 7000 多萬,原因就是這 7000 多萬資料都配置設定到一個 reduce 去執行,導緻資料傾斜。

解決方法:

  1. 可以先把身份證号為空的去除之後再關聯,最後按照 userkey 連接配接,因為 userkey 全部都是有值的:
with t1 as(
select
  u.userkey,
  o.*
from user_info u
left join user_order o
on u.idno = o.idno
where u.idno is not null

)

select
  a.userkey,
  a.idno,
  a.phone,
  a.name,
  b.user_active_at,
  c.intend_commodity,
  c.intend_rank,
  d.order_num,
  d.order_amount
from user_info a
left join user_active b on a.userkey = b.userkey
left join user_intend c on a.phone = c.phone
left join t1 d on a.userkey = d.userkey;      
  1. 也可以這樣,給身份證為空的資料賦個随機值,但是要注意随機值不能和表中的身份證号有重複:
select
  a.userkey,
  a.idno,
  a.phone,
  a.name,
  b.user_active_at,
  c.intend_commodity,
  c.intend_rank,
  d.order_num,
  d.order_amount
from user_info a
left join user_active b on a.userkey = b.userkey
left join user_intend c on a.phone = c.phone
left join user_order d on nvl(a.idno,concat(rand(),'idnumber')) = d.idno;      

其他的解決資料傾斜的方法:

1. 過濾掉髒資料

如果大 key 是無意義的髒資料,直接過濾掉。本場景中大 key 有實際意義,不能直接過濾掉。

2. 資料預處理

資料做一下預處理(如上面例子,對 null 值賦一個随機值),盡量保證 join 的時候,同一個 key 對應的記錄不要有太多。

3. 增加 reduce 個數

如果資料中出現了多個大 key,增加 reduce 個數,可以讓這些大 key 落到同一個 reduce 的機率小很多。

配置 reduce 個數:

set mapred.reduce.tasks = 15;      

4. 轉換為 mapjoin

如果兩個表 join 的時候,一個表為小表,可以用 mapjoin 做。

配置 mapjoin:

set hive.auto.convert.join = true;  是否開啟自動mapjoin,預設是true

set hive.mapjoin.smalltable.filesize=100000000;   mapjoin的表size大小      

5. 啟用傾斜連接配接優化

hive 中可以設定 ​

​hive.optimize.skewjoin​

​​ 将一個 join sql 分為兩個 job。同時可以設定下 ​

​hive.skewjoin.key​

​,此參數表示 join 連接配接的 key 的行數超過指定的行數,就認為該鍵是偏斜連接配接鍵,就對 join 啟用傾斜連接配接優化。預設 key 的行數是 100000。

配置傾斜連接配接優化:

set hive.optimize.skewjoin=true; 啟用傾斜連接配接優化

set hive.skewjoin.key=200000; 超過20萬行就認為該鍵是偏斜連接配接鍵      

6. 調整記憶體設定

适用于那些由于記憶體超限任務被 kill 掉的場景。通過加大記憶體起碼能讓任務跑起來,不至于被殺掉。該參數不一定會明顯降低任務執行時間。

配置記憶體:

set mapreduce.reduce.memory.mb=5120; 設定reduce記憶體大小

set mapreduce.reduce.java.opts=-Xmx5000m -XX:MaxPermSize=128m;      
附:Hive 配置屬性官方連結:https://cwiki.apache.org/confluence/display/Hive/Configuration+Properties