【Dr.Elephant中文文檔-8】調優建議

  • 2019 年 12 月 26 日
  • 筆記

你可以使用Dr. Elephant來分析你的作業(只需在搜索頁貼入你的作業ID),就可以知道你的作業有哪些地方需要優化。

1.加速你的作業流

一般對於特定的作業,最好有自己的配置。大多數情況下,作業的默認配置無法提供最佳性能。儘管作業調優比較費勁,但一些簡單的調整往往也能帶來不錯的效果。

需要特別注意的是mapperreducer的數量,io和記憶體使用的配置,以及生成的文件數量。對這幾個參數進行調整,讓參數更適合當前的任務,可以極大的提升任務的執行性能。

Apache的官網中Hadoop Map/Reduce Tutorial(http://hadoop.apache.org/docs/current/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html#Task_Execution__Environment)這篇文章提供很多詳細且有用的調試建議,有興趣的可以仔細看看。

2.常規建議

2.1.逐步調優很重要

對於Pig作業來說,如果使用默認參數來設置reducer的數量,這對作業的性能可能是致命的。一般來說,對每個Pig作業,都花一些時間來調優參數PARALLEL是非常值得做的。例如:

memberFeaturesGrouped = GROUP memberFeatures BY memberId PARALLEL 90;  

2.2.文件數vs塊數量

為了防止NameNode崩潰,存大文件比小文件更合理。NameNode每存儲一個文件大概消耗70 bytes,每存儲一個塊大概消耗60 byte。一般情況下,對於任務來說,使用一個較大的文件要比使用十個小文件的效率高一些。在大規模集群下,這10 byte的差距會越來越大。此外在許多情況下,1個大文件比10個小文件操作起來更高效。

2.3.Java任務記憶體管理

默認情況下,每個map/reduce作業可以分配最大2G的記憶體。對於java任務,這2G的空間既包括1G的堆記憶體,又包括0.5-1G的非堆記憶體。對於有些任務來說,默認的空間分配可能是不夠用的。下面列舉了一些能夠減少記憶體使用的技巧:

2.3.1.UseCompressedOops

32位JVM使用32bit無符號整型來定位記憶體區域,最大可定義的堆記憶體為(2^32 -1) = 4GB。64位的JVM虛擬機使用64bit的無符號Long整型來定位記憶體區域,最大可定義的堆記憶體大小為(2^64 - 1) = 16艾位元組。雖然定義的堆記憶體增加了,但是用Long代替int型,所需記憶體空間也增加了。大約為原來的1.5倍。這使得你可以突破1G堆空間的限制,對此你可以做些什麼呢?現在所有的JVM都支援UseCompressedOops選型,在某些情況下,他使用32bit的空間代替64bit空間來保存記憶體定位資訊。這將可以減少記憶體的佔用而不用回到32bit的情況。你可以在azkaban的作業文件中增加以下選項來實現:

hadoop-inject.mapreduce.(map|reduce).java.opts=-Xmx1G -XX:+UseCompressedOops  

注意azkaban默認會使用自定義的屬性覆蓋掉默認配置屬性,而不是將自定義的部分添加到mapred-site.xml默認文件中。你需要確認CompressedOops選項和其他默認的配置都是有效的。需要確認的是:"-Xmx1G"是配置文件mapred-site.xml中的,而其他的配置文件是我們自定義的。

2.3.2.UseCompressedStrings

這個選項會將String類型的變數轉化為byte[]類型來保存。如果一個任務中使用了大量的String類型變數,那麼這個選項將會極大的節約記憶體使用。在參數mapreduce.(map|reduce).java.opts配置中添加-XX:+UseCompressedString就會激活這個選項。每個作業分配的虛擬記憶體空間是需要的物理記憶體空間的2.1倍。如果我們程式拋出以下錯誤:

Container [pid=PID,containerID=container_ID]   is running beyond virtual memory limits. Current usage: 365.1 MB of 1  GB physical memory used; 3.2 GB of 2.1 GB virtual memory used.  Killing container  

你就可以嘗試使用這個選項來對程式進行優化。

2.4.關鍵調優參數

2.4.1.Mappers

mapreduce.input.fileinputformat.split.minsize

這個參數表示輸入到map中的每個文件塊切分的大小的最小值。通過增加dfs.blocksize的塊大小,可以增加每個map中輸入文件塊的大小,從而減少map的數量。這是因為如果說你設置mapreduce.input.fileinputformat.split.minsize的大小為HDFS塊大小(dfs.blocksize)的4倍時,那麼輸入到每個map的數量就是4倍的dfs.blocksize,這樣就減少了map的數量。如果把這個值設置為256MB,那麼輸入的文件大小就是268435456bit。

mapreduce.input.fileinputformat.split.maxsize

這個參數表示當使用CombineFileInputFormatMultiFileInputFormat時,輸入到map的每個文件的最大值。當這個值小於dfs.blocksize時,會增加作業的mapper的數量。這是因為如果說你設置mapreduce.input.fileinputformat.split.minsize的大小為HDFS塊大小(dfs.blocksize)的1/4時,這樣就將輸入到每個map的文件大小限制為dfs.blocksize的1/4,就增加了map的數量。這個值是輸入文件切分的值。因此要設置為256MB,你將指定值為268435456。需要注意的是,如果在使用CombineFileInputFormat時未設置最大分割大小,則作業將僅使用1個mapper來處理作業。(這可能是你不希望看到的)

2.4.2.Reducers

mapreduce.job.reduces

影響作業流性能的最大殺手之一是reducers的數量。reducers數量過少,可能會使任務時間超過15分鐘,而數量過多也同樣會有問題。針對每個特定的任務因地制宜的調整reducer數量是一項藝術。下面列了一些方法來幫助我們來設置合適的reducer數量:

  • reducers越多意味著Namenode上文件越多,過多的小文件可能會導致Namenode宕機。因此如果reduce輸出不大(小於512M),可以減少reducers的數量
  • reducers越多意味著每個reducer處理數據的時間越短,如果reducers數量過少。那麼每個reducer的消耗時間就會增加,reducer運行越快,就能處理更多的作業。

在大型任務中,清洗(Shuffling)操作的代價是比較高的。我們通過HDFS文件系統的各個計數器可以看到有大量的數據需要在不同的節點間進行交換。我們用20個reducers的作業來做個試驗,文件系統的計數器如下:FileSystemCounter:

  • FILE_BYTES_READ | 2950482442768
  • HDFS_BYTES_READ | 1223524334581
  • FILE_BYTES_WRITTEN | 5967256875163

我們可以看到有超過1K個map產生了約5TB的中間數據。再看下清洗時間:Shuffle Finished:17-Aug-2010 | 13:32:05 | ( 1hrs, 29mins, 56 sec)

Sort Finished:17-Aug-2010 | 14:18:35 | (46mins, 29sec)

可以看出,大約有5TB的數據花費了1個半小時來清洗,然後有花了46分鐘來進行排序。這個時間成本是巨大的。我們希望任務可以在5-15分鐘內完成。我們現在已經解決了這個問題。讓我們來算一下:使用20個reducers需要消耗360分鐘,200個reducers則需要36分鐘,400個reducers則需要18分鐘。因此圍繞這個邏輯來進行改進,將reducers控制在500個以下則出現以下結果:Shuffle Finished:17-Aug-2010 | 16:32:32 | ( 12 mins, 46 sec)

Sort Finished:17-Aug-2010 | 16:32:37 | (4sec) 效果看著還不錯,通過一些調優,我們可以縮短一些任務時間。正如你猜的那樣,反過來也一樣。如果清洗時間很短,CPU使用也很少,那麼說明reducer數量過少,合適的配置需要通過不斷的試驗來確定。

mapreduce.job.reduce.slowstart.completedmaps

這個參數決定了在reducer開始執行之前,至少有多少比例的mapper必須執行結束。默認值是80%。對於大多數任務,調整這個數字的大小可能會帶來性能提升。決定這個數字的因素是:

  • 每個reducer接收到多少數據
  • 剩下的map每一個map作業需要花費的時間 如果map的輸出數據量比較大,一般會建議讓reducer提前開始執行去處理這些數據。如果map任務產生的數量不是很大,一般建議讓reducer的執行時間開始的晚一些。對於清洗過程的一個粗略的時間估計是:當所有的mapper結束後開始,到第一個reducer開始執行的時間為止。這是reducer獲得所有的輸入所需要的時間。一般認為,reducers開始執行時間是:最後一個map結束時間+清洗時間。

2.4.3.壓縮

mapreduce.map.output.compress

將這個參數設置為True可以將map輸出的數據進行壓縮。這可以減少節點間的數據交互,但我們必須確保解壓縮的時間要比傳輸時間短,否則會起反作用。對於大型或可以高度壓縮的map輸出數據,這個參數選型非常有必要,將大量減少清洗時間。對於小型map輸出數據集,關閉這個參數,將降低解壓縮帶來的CPU消耗時間。注意與mapreduce.output.fileoutputformat.compress選項不同,那個參數決定了任務的輸出回寫到HDFS時是否需要壓縮。

2.4.4.記憶體

mapreduce.(map|reduce).memory.mb

新版Hadoop中增加了堆記憶體的限制特性。這使得系統在繁忙情況下更好的管理資源分配。默認情況下,系統會分配給Java任務1GB的堆記憶體,以及0.5-1GB的非堆記憶體空間。因此,mapreduce.(map|reduce).memory.mb的默認值為2GB。在某些情況下,這個記憶體值是不夠用的。如果只是設置Java的參數-Xmx,任務會被殺死,需要同時設置參數mapreduce.(map|reduce).momory.mb才能有效的提升或者限制記憶體使用。

2.4.5.進階

控制io.sort.record.percent的值

參數io.sort.record.percent決定使用多少的間接快取空間來保存每條和每條記錄的元資訊。一般來說,這個參數的設置是不合理的。

假設在map作業中使用日誌的xml的配置文件:

property

value

bufstart

45633950

bufend

68450908

kvstart

503315

kvend

838860

length

838860

io.sort.mb

256

io.sort.record.percent

0.05

使用上面的值:

property

value

io.sort.spill.percent (length-kvstart+kvend) / length)

0.8

Size of meta data is (bufend-bufstat)

22816958 (MB)

Records in memory (length-(kvstart+kvend))

671087

Average record size (size/records)

34 Bytes

Record+Metadata

50 Bytes

Records per io.sort.mb (io.sort.mb/(record+metadata))

5.12 million

Metadata % in io.sort.mb ((records per io.sort.mb)*metadata/io.sort.mb)

0.32

我們可以在256MB的快取中保存很多數據。但是io.sort.record.percent應該設置為0.32,而不是0.5。當設置為0.5時,記錄的元資訊的快取會比記錄的快取更大。

調整這個參數會使map運行的更快,磁碟溢出問題更少,因為io.sort.mb的效率更高了;不會再很快就使用完元數據緩衝區的80%空間。

調整io.sort.record.percent後會使得許多map數據不再溢出到磁碟,減少了55%的磁碟溢。最終,系統節省了30%的CPU資源消耗,和30分鐘運行時間。

mapreduce.(map|reduce).speculative

這個參數決定了是否允許相同的map/reduce並行執行。你大概知道當發生數據傾斜時,有些mapperreducer會運行很長時間。在這種情況下,你可能希望通過一些預判來防止數據傾斜。

2.4.6.Pig

Pig中你可以通過增加以下命令來設置HadoopPig

SET <property_name> <property_value>;  

例如,如果你的map記憶體不足,可以通過以下命令增加記憶體

SET mapreduce.map.memory.mb 4096;  

Azkaban中,可以通過以下命令實現

jvm.args=-Dmapreduce.map.memory.mb=4096  to your job properties.  
pig.maxCombinedSplitSize / 增加或減少mapper數量

默認情況下,Pig合併了小文件(pig.splitCombination默認為true),直到需要切分的HDFS塊大小超過512MB。要進一步增加這個值,需要調高pig.maxCombinedSplitSize。相關詳情可以看這裡(https://pig.apache.org/docs/r0.11.1/perf.html#combine-files)。你可以在你的Pig腳本中添加以下命令

set pig.maxCombinedSplitSize <size-in-bytes>;  

在你的Pig腳本的開頭。如果您通過Azkaban執行此Pig腳本,您也可以通過添加以下命令來設置他

jvm.args=-Dpig.maxCombinedSplitSize=<size-in-bytes>  

在你的作業屬性中。如果你的mapper耗時太長,想增加mapper數量,你必須同時設置

set pig.maxCombinedSplitSize <size-in-bytes>;  set mapreduce.input.fileinputformat.split.maxsize <size-in-bytes>;  

如果值小於512MB。是因為Pig拆分塊的值超過了pig.maxCombinedSplitSize,拆分大小由以下配置決定

max(mapreduce.input.fileinputformat.split.minsize, min(mapreduce.input.fileinputformat.split.maxsize, dfs.blocksize))  

提供一些集群設置:

mapreduce.input.fileinputformat.split.minsize=0  mapreduce.input.fileinputformat.split.maxsize is unset  dfs.blocksize=536870912 // 512 MB  will evaluate to 512 MB.  
Reducers數量

Pig中,你可以基於每個作業控制Reducer的數量,還可以選擇為整個腳本設置默認的reducers數量。瀏覽此處獲取更多資訊。

2.4.7.Hive

  • mapreduce.input.fileinputformat.split.minsize
  • mapreduce.input.fileinputformat.split.maxsize
  • mapreduce.input.fileinputformat.split.minsize.per.node
  • mapreduce.input.fileinputforomat.split.minsize.per.rack

對應Hive來說,你可能需要設置以上4個參數來調整切分大小。例如:

-- The following are the default configurations on our Hadoop clusters.  set mapreduce.input.fileinputformat.split.maxsize                 = 2147483648;  set mapreduce.input.fileinputformat.split.minsize                 = 1073741824;  set mapreduce.input.fileinputformat.split.minsize.per.node        = 1073741824;  set mapreduce.input.fileinputformat.split.minsize.per.rack        = 1073741824;  

如果你想增加mapper的數量,就減少這些配置的值;反之,則增加這些配置的值。

dr.elephant系列基礎文章到此就完結了,後續會整理成合集,並推出實戰問題解決方案。