如何控制Hive執行中的Map和Reduce數量
目錄
Map
map任務的個數
map任務的個數是在Map階段中InputSplit決定的,InputSplit將作業的Input目錄下的檔案切片,每個片大小預設是等於block塊大小的。所以不同的map個數的控制方法從根本上都是來控制輸入檔案的大小和個數來進行實現的。主要決定因素有input的檔案總個數,input的檔案大小,叢集設定的檔案塊大小(目前為128M,可在hive中通過set dfs.block.size;命令檢視到,該引數不能自定義修改。)
切片與塊的關係
- 假設input目錄下有1個檔案a,大小為800M,那麼hadoop會將該檔案a分割成7塊(6塊為128M,1塊為32M),從而在執行mapreduce的時候會產生7個map任務(InputSplit切片大小預設等於塊大小,若最後一個切片大小小於塊大小的%10,那麼它會跟上一個切片合併為一個切片,即6個map數)。
- 假設input目錄下有3個檔案a,b,c,大小分別為10M,20M和130.,那麼hadoop會將這3個檔案分隔成4個塊(10M,20M,128M,2M)。在執行mapreduce的時候會被InputSplit切成3個切片,即產生3個map任務。
- 切片是邏輯上的,而塊是物理上的。
- 一般切片大小事預設等於塊大小的,若一個切片包含多個block,總會從其他節點讀取資料,也就是做不到所有的計算都是本地化,所以應該儘量使InputSplit切片大小與block大小相當。
是不是map數越多越好
如果一個任務有很多小檔案(遠遠小於塊大小128M),則每個小檔案也會被當做一個塊,用一個map任務來完成,而一個map任務啟動和初始化的時間遠遠大於邏輯處理的時間,就會造成很大的資源浪費。而且,同時可執行的map數是受限制的。
故並不是map數越多越好
合理的map數設定
比如有一個127M的檔案,正常會用一個map任務去完成,但這個檔案只有一個或者兩個小欄位,卻有幾千萬的記錄,如果map處理的邏輯比較複雜,用一個map任務去做,肯定也比較耗時。
針對上面的問題3和4,我們需要採用兩種方式來解決:即減少map數和增加map數。
如何合併小檔案,減少map數?
假設一個SQL任務:
Select count(1) from popt_tbaccountcopy_mes where pt = ‘2012-07-04’;
該任務的input目錄:
/group/p_sdo_data/p_sdo_data_etl/pt/popt_tbaccountcopy_mes/pt=2012-07-04
共有194個檔案,其中很多是遠遠小於128M的小檔案,總大小9G,正常執行會用194個map任務。
Map總共消耗的計算資源:SLOTS_MILLIS_MAPS=623020
通過以下方法在map執行前合併小檔案,減少map數:
set mapred,.max.split.size=100000000;(10000000代表了100M)
set mapred.min.split.size.per.node=100000000;
set mapred.min.split.size.per,rack=100000000;
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
再執行上面的語句,用了74個map任務,map消耗的計算資源:
SLOTS_MILLIS_MAPS=333500
對於這個簡單SQL任務,執行時間上可能差不多,但節省了一半的計算資源。
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;這個引數指定了執行前進行小檔案合併的類。
前面三個引數確定合併檔案塊的大小,大於檔案塊大小128M的,按照128M來切分,小於128M,大於100M的,按照100M來切分,那些小於100M的(包括小檔案和分隔大檔案剩下的)按照指定的合併類進行合併,最終生成了74個塊。
如何適當的增加map數?
當input的檔案都很大,任務邏輯複雜,map執行非常慢的時候,可以考慮增加map數,來使得每個map處理的資料量減少,從而提高任務的執行效率。
假設有這樣一個任務:
select data_desc,
count(1),
count(distinct id),
sum(case when…),
sum(case when…),
sum(…)
from a group by data_desc
如果表a只有一個檔案,大小為120M,但包含幾千萬的記錄,如果用1個map去完成這個任務,肯定是比較耗時的,這種情況下,我們要考慮將這一個檔案合理的拆分成多個,這樣就可以用多個map任務去完成。
set mapred.map.tasks=10;
create table a_1 as
select * from a
distribute by rand(123);
這樣會將a表的記錄,隨機的分散到包含10個檔案的a_1表中,再用a_1代替上面sql中的a表,則會用10個map任務去完成。每個map任務處理大於12M(幾百萬記錄)的資料,效率肯定會好很多。
控制map的個數主要就是兩個思路:合併小檔案、將大檔案拆分成小檔案。在方法實現上遵循兩個原則:使大資料量利用合適的map數;使單個map任務處理合適的資料量。
Reduce
hive自己如何確定reduce數
reduce個數的設定極大的影響了任務執行效率,不指定reduce個數的情況下,Hive會猜測確定一個reduce個數,基於以下兩個設定:
hive.exec.reducers.bytes.per.reducer(每個reduce任務處理的資料量,預設為1000^3=1G)
hive.exec.reducer.max(每個任務最大的reduce數,預設為999)
計算reducer數的公式很簡單N=min(引數2,總輸入資料量/引數1)
即,如果reduce的輸入(map的輸出)總大小不超過1G,那麼只會有一個reduce任務;如:
select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt; /group/p_sdo_data/p_sdo_data_etl/pt/popt_tbaccountcopy_mes/pt=2012-07-04
總大小為9G多,因此這個sql任務有10個reduce。
調整reduce個數方法
調整hive.exec.reducers.bytes.per.reducer引數的值
set hive.exec.reducers.bytes.per.reducer=500000000;(500M)
select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt;
這次會有20個reduce。
調整mapred.reduce.tasks引數的值
set mapred.reduce.tasks=15;
select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt;
那麼這次會有15個reduce。
是不是reduce個數越多越好
同map一樣,啟動和初始化reduce也會消耗時間和資源;
另外,有多少個reduce,就會有多少個輸出檔案,如果生成了很多小檔案,那麼如果這些小檔案作為下一個任務的輸入,則也會出現小檔案過多的問題.
只有一個reduce的情況
很多時候你會發現任務中不管資料量多大,不管你有沒有設定調整reduce個數的引數,任務中一直都只有一個reduce任務; 其實只有一個reduce任務的情況,除了資料量小於hive.exec.reducers.bytes.per.reducer引數值的情況外,還有以下原因:
a) 沒有group by的彙總,比如把select pt,count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04' group by pt; 寫成 select count(1) from popt_tbaccountcopy_mes where pt = '2012-07-04'。
b) 用了Order by
c) 有笛卡爾積 通常這些情況下,除了找辦法來變通和避免,暫時沒有什麼好的辦法,因為這些操作都是全域性的,所以hadoop不得不用一個reduce去完成; 同樣的,在設定reduce個數的時候也需要考慮這兩個原則:使大資料量利用合適的reduce數;使單個reduce任務處理合適的資料量;