【Dr.Elephant中文文檔-8】調優建議
- 2019 年 12 月 26 日
- 筆記
你可以使用Dr. Elephant
來分析你的作業(只需在搜索頁貼入你的作業ID),就可以知道你的作業有哪些地方需要優化。
1.加速你的作業流
一般對於特定的作業,最好有自己的配置。大多數情況下,作業的默認配置無法提供最佳性能。儘管作業調優比較費勁,但一些簡單的調整往往也能帶來不錯的效果。
需要特別注意的是mapper
和reducer
的數量,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
的數量。如果把這個值設置為256
MB,那麼輸入的文件大小就是268435456
bit。
mapreduce.input.fileinputformat.split.maxsize
這個參數表示當使用CombineFileInputFormat
和MultiFileInputFormat
時,輸入到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
並行執行。你大概知道當發生數據傾斜時,有些mapper
或reducer
會運行很長時間。在這種情況下,你可能希望通過一些預判來防止數據傾斜。
2.4.6.Pig
在Pig
中你可以通過增加以下命令來設置Hadoop
和Pig
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
系列基礎文章到此就完結了,後續會整理成合集,並推出實戰問題解決方案。