目錄
map階段優化
充分利用Combiner
Combiner在Map端提前進行了一次Reduce處理。
可減少Map Task中間輸出的結果,從而減少各個Reduce Task的遠程拷貝數據量,最終表現爲Map Task和Reduce Task執行時間縮短。
選擇合理的Writable類型
爲應用程序處理的數據選擇合適的Writable類型可大大提升性能。
比如處理整數類型數據時,直接採用IntWritable比先以Text類型讀入在轉換爲整數類型要高效。
如果輸出整數的大部分可用一個或兩個字節保存,那麼直接採用VIntWritable或者VLongWritable,它們採用了變長整型的編碼方式,可以大大減少輸出數據量。
增加輸入文件的副本數,就是充分利用本地讀
假設集羣有1個Namenode+8個Datanode節點,HDFS默認的副本數爲3 ,那麼map端讀取數據的時候,在啓動map task的機器上讀取本地的數據爲3/8,一部分數據是通過網絡從其他節點拿到的
那麼如果副本數設置爲8會是什麼情況?
相當於每個子節點上都會有一份完整的數據,map讀取的時候直接從本地拿,不需要通過網絡這一層了,但是在實際情況中設置副本數爲8是不可行的,因爲數據本身非常龐大,副本數超過5對集羣的磁盤就非常有壓力了,所以這項設置需要酌情處理
提前合併map小文件,減少map 數量
map階段的第一步,從磁盤讀取數據並切片,每個分片由一個map task處理,當輸入的是海量的小文件的時候,會啓動大量的map task,效率及其之慢,有效的解決方式是使用CombineInputFormat自定義分片策略對小文件進行合併處理 ,從而減少map task的數量,減少map過程使用的時間
map task的啓動數量也和下面這幾個參數有關係:
- mapred.min.split.size:Input Split的最小值 默認值1
當mapred.min.split.size小於dfs.block.size的時候,一個block會被分爲多個分片,也就是對應多個map task
當mapred.min.split.size大於dfs.block.size的時候,一個分片可能對應多個block,也就是一個map task讀取多個block數據 - mapred.max.split.size:Input Split的最大值
- dfs.block.size:HDFS 中一個block大小,默認值128MB
提高block大小
集羣的網絡、IO等性能很好的時候,建議調高dfs.block.size
根據數據源的特性,主要調整mapred.min.split.size來控制map task的數量
提高map輸出的Buffer
該階段是map side中將結果輸出到磁盤之前的一個處理方式,通過對其進行設置的話可以減少map任務的IO開銷,從而提高性能。
由於map任務運行時中間結果首先存儲在buffer中,默認當緩存的使用量達到80%的時候就開始寫入磁盤,這個過程叫做spill(溢出)
這個buffer默認的大小是100M可以通過設定io.sort.mb的值來進行調整
當map產生的數據非常大時,如果默認的buffer大小不夠看,那麼勢必會進行非常多次的spill,進行spill就意味着要寫磁盤,產生IO開銷 ,這時候就可以把io.sort.mb調大,那麼map在整個計算過程中spill的次數就勢必會降低,map task對磁盤的操作就會變少
如果map tasks的瓶頸在磁盤上,這樣調整就會大大提高map的計算性能,但是如果將io.sort.mb調的非常大的時候,對機器的配置要求就非常高,因爲佔用內存過大,所以需要根據情況進行配置
map並不是要等到buffer全部寫滿時才進行spill,因爲如果全部寫滿了再去寫spill,勢必會造成map的計算部分等待buffer釋放空間的情況。
所以,map其實是當buffer被寫滿到一定程度(比如80%)時,纔開始進行spill
可以通過設置io.sort.spill.percent的值來調整這個閾值
這個參數同樣也是影響spill頻繁程度,進而影響map task運行週期對磁盤的讀寫頻率,但是通常情況下只需要對io.sort.mb進行調整即可
合理配置map的Merge參數
該階段是map產生spill之後,對spill進行處理的過程,通過對其進行配置也可以達到優化IO開銷的目的,map產生spill之後必須將些spill進行合併,這個過程叫做merge ,merge過程是並行處理spill的,每次並行多少個spill是由參數io.sort.factor指定的,默認爲10個,如果產生的spill非常多,merge的時候每次只能處理10個spill,那麼還是會造成頻繁的IO處理 ,適當的調大每次並行處理的spill數有利於減少merge數因此可以影響map的性能,但是如果調整的數值過大,並行處理spill的進程過多會對機器造成很大壓力
Combine的使用策略
我們知道如果map side設置了Combiner,那麼會根據設定的函數對map輸出的數據進行一次類reduce的預處理
但是和分組、排序分組不一樣的是,combine發生的階段可能是在merge之前,也可能是在merge之後
這個時機可以由一個參數控制:min.num.spill.for.combine,默認值爲3
當job中設定了combiner,並且spill數最少有3個的時候,那麼combiner函數就會在merge產生結果文件之前運行
例如,產生的spill非常多,雖然我們可以通過merge階段的io.sort.factor進行優化配置,但是在此之前我們還可以通過先執行combine對結果進行處理之後再對數據進行merge ,這樣一來,到merge階段的數據量將會進一步減少,IO開銷也會被降到最低輸出中間數據到磁盤
這個階段是map side的最後一個步驟,在這個步驟中也可以通過壓縮選項的配置來得到任務的優化
合理的開啓map壓縮
其實無論是spill的時候,還是最後merge產生的結果文件,都是可以壓縮的
壓縮的好處在於,通過壓縮減少寫入讀出磁盤的數據量。對中間結果非常大,磁盤速度成爲map執行瓶頸的job,尤其有用
控制輸出是否使用壓縮的參數是mapred.compress.map.output,值爲true或者false
啓用壓縮之後,會犧牲CPU的一些計算資源,但是可以節省IO開銷,非常適合IO密集型的作業(如果是CPU密集型的作業不建議設置)
設置壓縮的時候,我們可以選擇不同的壓縮算法
Hadoop默認提供了GzipCodec,LzoCodec,BZip2Codec,LzmaCodec等壓縮格式
通常來說,想要達到比較平衡的cpu和磁盤壓縮比,LzoCodec比較合適,但也要取決於job的具體情況
如果想要自行選擇中間結果的壓縮算法,可以設置配置參數:
mapred.map.output.compression.codec=org.apache.hadoop.io.compress.DefaultCodec
Map side tuning總結
map端調優的相關參數:
選項 | 類型 | 默認值 | 描述 |
---|---|---|---|
mapred.min.split.size | int | 1 | Input Split的最小值 |
mapred.max.split.size | int | - | Input Split的最大值 |
io.sort.mb | int | 100 | map緩衝區大小 |
io.sort.spill.percent | float | 0.8 | 緩衝區閾值 |
io.sort.factor | int | 10 | 並行處理spill的個數 |
min.num.spill.for.combine | int | 3 | 最少有多少個spill的時候combine在merge之前進行 |
mapred.compress.map.output | boolean | false | map中間數據是否採用壓縮 |
mapred.map.output.compression.codec | String | - | 壓縮算法 |
Shuffle階段優化
Copy數據階段
由於job的每一個map都會根據reduce(n)數將數據分成map 輸出結果分成n個partition,所以map的中間結果中是有可能包含每一個reduce需要處理的部分數據的
爲了優化reduce的執行時間,hadoop中等第一個map結束後,所有的reduce就開始嘗試從完成的map中下載該reduce對應的partition部分數據
在這個shuffle過程中,由於map的數量通常是很多個的,而每個map中又都有可能包含每個reduce所需要的數據
所以對於每個reduce來說,去各個map中拿數據也是並行的,可以通過mapred.reduce.parallel.copies這個參數來調整,默認爲5
當map數量很多的時候,就可以適當調大這個值,減少shuffle過程使用的時間
還有一種情況是:reduce從map中拿數據的時候,有可能因爲中間結果丟失、網絡等其他原因導致map任務失敗
而reduce不會因爲map失敗就永無止境的等待下去,它會嘗試去別的地方獲得自己的數據(這段時間失敗的map可能會被重跑)
所以設置reduce獲取數據的超時時間可以避免一些因爲網絡不好導致無法獲得數據的情況
mapred.reduce.copy.backoff,默認300s
一般情況下不用調整這個值,因爲生產環境的網絡都是很流暢的
Merge階段
由於reduce是並行將map結果下載到本地,所以也是需要進行merge的,所以io.sort.factor的配置選項同樣會影響reduce進行merge時的行爲
和map一樣,reduce下載過來的數據也是存入一個buffer中而不是馬上寫入磁盤的,所以我們同樣可以控制這個值來減少IO開銷
控制該值的參數爲:
mapred.job.shuffle.input.buffer.percent,默認0.7,這是一個百分比,意思是reduce的可用內存中拿出70%作爲buffer存放數據
reduce的可用內存通過mapred.child.java.opts來設置,比如置爲-Xmx1024m,該參數是同時設定map和reduce task的可用內存,一般爲map buffer大小的兩倍左右
設置了reduce端的buffer大小,我們同樣可以通過一個參數來控制buffer中的數據達到一個閾值的時候開始往磁盤寫數據:mapred.job.shuffle.merge.percent,默認爲0.66
歸併排序Sort
sort的過程一般非常短,因爲是邊copy邊merge邊sort的,後面就直接進入真正的reduce計算階段了
Reduce
之前我們說過reduc端的buffer,默認情況下,數據達到一個閾值的時候,buffer中的數據就會寫入磁盤,然後reduce會從磁盤中獲得所有的數據
也就是說,buffer和reduce是沒有直接關聯的,中間多個一個寫磁盤->讀磁盤的過程,既然有這個弊端,那麼就可以通過參數來配置
使得buffer中的一部分數據可以直接輸送到reduce,從而減少IO開銷:mapred.job.reduce.input.buffer.percent,默認爲0.0
當值大於0的時候,會保留指定比例的內存讀buffer中的數據直接拿給reduce使用
這樣一來,設置buffer需要內存,讀取數據需要內存,reduce計算也要內存,所以要根據作業的運行情況進行調整
reduce調優主要參數:
選項 | 類型 | 默認值 | 描述 |
---|---|---|---|
mapred.reduce.parallel.copies | int | 5 | 每個reduce去map中拿數據的並行數 |
mapred.reduce.copy.backoff | int | 300 | 獲取map數據最大超時時間 |
mapred.job.shuffle.input.buffer.percent | float | 0.7 | buffer大小佔reduce可用內存的比例 |
mapred.child.java.opts | String | - | -Xmx1024m設置reduce可用內存爲1g |
mapred.job.shuffle.merge.percent | float | 0.66 | buffer中的數據達到多少比例開始寫入磁盤 |
mapred.job.reduce.input.buffer.percent | float | 0.0 | 指定多少比例的內存用來存放buffer中的數據 |