您好,登錄后才能下訂單哦!
本篇內容介紹了“提高MapReduce性能的方法有哪些”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
Cloudera提供給客戶的服務內容之一就是調整和優化MapReduce job執行性能。MapReduce和HDFS組成一個復雜的分布式系統,并且它們運行著各式各樣用戶的代碼,這樣導致沒有一個快速有效的規則來實現優化代碼性能的目的。在我看來,調整cluster或job的運行更像一個醫生對待病人一樣,找出關鍵的“癥狀”,對于不同的癥狀有不同的診斷和處理方式。
在醫學領域,沒有什么可以代替一位經驗豐富的醫生;在復雜的分布式系統上,這個道理依然正確—有經驗的用戶和操作者在面對很多常見問題上都會有“第六感”。我曾經為Cloudera不同行業的客戶解決過問題,他們面對的工作量、數據集和cluster硬件有很大區別,因此我在這方面積累了很多的經驗,并且想把這些經驗分享給諸位。
在這篇blog里,我會高亮那些提高MapReduce性能的建議。前面的一些建議是面向整個cluster的,這可能會對cluster 操作者和開發者有幫助。后面一部分建議是為那些用Java編寫MapReduce job的開發者而提出。在每一個建議中,我列出一些“癥狀”或是“診斷測試”來說明一些針對這些問題的改進措施,可能會對你有所幫助。
請注意,這些建議中包含很多我以往從各種不同場景下總結出來的直觀經驗。它們可能不太適用于你所面對的特殊的工作量、數據集或cluster,如果你想使用它,就需要測試使用前和使用后它在你的cluster環境中的表現。對于這些建議,我會展示一些對比性的數據,數據產生的環境是一個4個節點的cluster來運行40GB的Wordcount job。應用了我以下所提到的這些建議后,這個job中的每個map task大概運行33秒,job總共執行了差不多8分30秒。
第一點 正確地配置你的Cluster
診斷結果/癥狀:
1. Linux top命令的結果顯示slave節點在所有map和reduce slot都有task運行時依然很空閑。
2. top命令顯示內核的進程,如RAID(mdX_raid*)或pdflush占去大量的CPU時間。
3. Linux的平均負載通常是系統CPU數量的2倍。
4. 即使系統正在運行job,Linux平均負載總是保持在系統CPU數量的一半的狀態。
5. 一些節點上的swap利用率超過幾MB
優化你的MapReduce性能的第一步是確保你整個cluster的配置文件被調整過。對于新手,請參考這里關于配置參數的一篇blog:配置參數。 除了這些配置參數 ,在你想修改job參數以期提高性能時,你應該參照下我這里的一些你應該注意的項:
1. 確保你正在DFS和MapReduce中使用的存儲mount被設置了noatime選項。這項如果設置就不會啟動對磁盤訪問時間的記錄,會顯著提高IO的性能。
2. 避免在TaskTracker和DataNode的機器上執行RAID和LVM操作,這通常會降低性能
3. 在這兩個參數mapred.local.dir和dfs.data.dir 配置的值應當是分布在各個磁盤上目錄,這樣可以充分利用節點的IO讀寫能力。運行 Linux sysstat包下的iostat -dx 5命令可以讓每個磁盤都顯示它的利用率。
4. 你應該有一個聰明的監控系統來監控磁盤設備的健康狀態。MapReduce job的設計是可容忍磁盤失敗,但磁盤的異常會導致一些task重復執行而使性能下降。如果你發現在某個TaskTracker被很多job中列入黑名單,那么它就可能有問題。
5. 使用像Ganglia這樣的工具監控并繪出swap和網絡的利用率圖。如果你從監控的圖看出機器正在使用swap內存,那么減少mapred.child.java.opts屬性所表示的內存分配。
基準測試:
很遺憾我不能為這個建議去生成一些測試數據,因為這需要構建整個cluster。如果你有相關的經驗,請把你的建議及結果附到下面的留言區。
第二點 使用LZO壓縮
診斷結果/癥狀:
1. 對 job的中間結果數據使用壓縮是很好的想法。
2. MapReduce job的輸出數據大小是不可忽略的。
3. 在job運行時,通過linux top 和 iostat命令可以看出slave節點的iowait利用率很高。
幾乎每個Hadoop job都可以通過對map task輸出的中間數據做LZO壓縮獲得較好的空間效益。盡管LZO壓縮會增加一些CPU的負載,但在shuffle過程中會減少磁盤IO的數據量,總體上總是可以節省時間的。
當一個job需要輸出大量數據時,應用LZO壓縮可以提高輸出端的輸出性能。這是因為默認情況下每個文件的輸出都會保存3個幅本,1GB的輸出文件你將要保存3GB的磁盤數據,當采用壓縮后當然更能節省空間并提高性能。
為了使LZO壓縮有效,請設置參數mapred.compress.map.output值為true。
基準測試:
在我的cluster里,Wordcount例子中不使用LZO壓縮的話,job的運行時間只是稍微增加。但FILE_BYTES_WRITTEN計數器卻從3.5GB增長到9.2GB,這表示壓縮會減少62%的磁盤IO。在我的cluster里,每個數據節點上磁盤數量對task數量的比例很高,但Wordcount job并沒有在整個cluster中共享,所以cluster中IO不是瓶頸,磁盤IO增長不會有什么大的問題。但對于磁盤因很多并發活動而受限的環境來說,磁盤IO減少60%可以大幅提高job的執行速度。
第三點 調整map和reduce task的數量到合適的值
自己的經驗,一般來說,不可能跑一個job, 改變整個集群的hdfs block的大小。通常提交job時候設置參數。
job.setMapOutputValueClass(IntWritable.class);
job.setNumReduceTasks(1);
//設置最小分片為512M
FileInputFormat.setMinInputSplitSize(job, 1024*1024*512);
FileInputFormat.addInputPath(job, new Path("/usr/keyword/input"));
診斷結果/癥狀:
1. 每個map或reduce task的完成時間少于30到40秒。
2. 大型的job不能完全利用cluster中所有空閑的slot。
3. 大多數map或reduce task被調度執行了,但有一到兩個task還在準備狀態,在其它task完成之后才單獨執行
調整job中map和reduce task的數量是一件很重要且常常被忽略的事情。下面是我在設置這些參數時的一些直觀經驗:
1. 如果每個task的執行時間少于30到40秒,就減少task的數量。Task的創建與調度一般耗費幾秒的時間,如果task完成的很快,我們就是在浪費時間。同時,設置JVM重用也可以解決這個問題。
2. 如果一個job的輸入數據大于1TB,我們就增加block size到256或者512,這樣可以減少task的數量。你可以使用這個命令去修改已存在文件的block size: hadoop distcp -Ddfs.block.size=$[256*1024*1024] /path/to/inputdata /path/to/inputdata-with/largeblocks。在執行完這個命令后,你就可以刪除原始的輸入文件了(/path/to/inputdata)。
3. 只要每個task運行至少30到40秒,那么就增加map task的數量,增加到整個cluster上map slot總數的幾倍。如果你的cluster中有100個map slot,那就避免運行一個有101個map task的job — 如果運行的話,前100個map同時執行,第101個task會在reduce執行之前單獨運行。這個建議對于小型cluste和小型job是很重要的。
4. 不要調度太多的reduce task — 對于大多數job來說,我們推薦reduce task的數量應當等于或是略小于cluster中reduce slot的數量。
基準測試:
為了讓Wordcount job有很多的task運行,我設置了如下的參數:Dmapred.max.split.size=$[16*1024*1024]。以前默認會產生360個map task,現在就會有2640個。當完成這個設置之后,每個task執行耗費9秒,并且在JobTracker的Cluster Summar視圖中可以觀看到,正在運行的map task數量在0到24之間浮動。job在17分52秒之后結束,比原來的執行要慢兩倍多。
第四點 為job添加一個Combiner
診斷結果/癥狀:
1. job在執行分類的聚合時,REDUCE_INPUT_GROUPS計數器遠小于REDUCE_INPUT_RECORDS計數器。
2. job執行一個大的shuffle任務(例如,map的輸出數據每個節點就是好幾個GB)。
3. 從job計數器中看出,SPILLED_RECORDS遠大于MAP_OUTPUT_RECORDS。
如果你的算法涉及到一些分類的聚合,那么你就可以使用Combiner來完成數據到達reduce端之前的初始聚合工作。MapReduce框架很明智地運用Combiner來減少寫入磁盤以及通過網絡傳輸到reduce端的數據量。
基準測試:
我刪去Wordcount例子中對setCombinerClass方法的調用。僅這個修改就讓map task的平均運行時間由33秒增長到48秒,shuffle的數據量也從1GB提高到1.4GB。整個job的運行時間由原來的8分30秒變成15分42秒,差不多慢了兩倍。這次測試過程中開啟了map輸出結果的壓縮功能,如果沒有開啟這個壓縮功能的話,那么Combiner的影響就會變得更加明顯。
第五點 為你的數據使用最合適和簡潔的Writable類型
診斷/癥狀:
1. Text 對象在非文本或混合數據中使用。
2. 大部分的輸出值很小的時候使用IntWritable 或 LongWritable對象。
當一個開發者是初次編寫MapReduce,或是從開發Hadoop Streaming轉到Java MapReduce,他們會經常在不必要的時候使用Text 對象。盡管Text對象使用起來很方便,但它在由數值轉換到文本或是由UTF8字符串轉換到文本時都是低效的,且會消耗大量的CPU時間。當處理那些非文本的數據時,可以使用二進制的Writable類型,如IntWritable, FloatWritable等。
除了避免文件轉換的消耗外,二進制Writable類型作為中間結果時會占用更少的空間。當磁盤IO和網絡傳輸成為大型job所遇到的瓶頸時,減少些中間結果的大小可以獲得更好的性能。在處理整形數值時,有時使用VIntWritable或VLongWritable類型可能會更快些—這些實現了變長整形編碼的類型在序列化小數值時會更節省空間。例如,整數4會被序列化成單字節,而整數10000會被序列化成兩個字節。這些變長類型用在統計等任務時更加有效,在這些任務中我們只要確保大部分的記錄都是一個很小的值,這樣值就可以匹配一或兩個字節。
如果Hadoop自帶的Writable類型不能滿足你的需求,你可以開發自己的Writable類型。這應該是挺簡單的,可能會在處理文本方面更快些。如果你編寫了自己的Writable類型,請務必提供一個RawComparator類—你可以以內置的Writable類型做為例子。
基準測試:
對于Wordcount例子,我修改了它在map計數時的中間變量,由IntWritable改為Text。并且在reduce統計最終和時使用Integer.parseString(value.toString)來轉換出真正的數值。這個版本比原始版本要慢近10%—整個job完成差不多超過9分鐘,且每個map task要運行36秒,比之前的33秒要慢。盡量看起來整形轉換還是挺快的,但這不說明什么情況。在正常情況下,我曾經看到過選用合適的Writable類型可以有2到3倍的性能提升的例子。
第六點 重用Writable類型
診斷/癥狀:
1. 在mapred.child.java.opts參數上增加-verbose:gc -XX:+PriintGCDetails,然后查看一些task的日志。如果垃圾回收頻繁工作且消耗一些時間,你需要注意那些無用的對象。
2. 在你的代碼中搜索"new Text" 或"new IntWritable"。如果它們出現在一個內部循環或是map/reduce方法的內部時,這條建議可能會很有用。
3. 這條建議在task內存受限的情況下特別有用。
很多MapReduce用戶常犯的一個錯誤是,在一個map/reduce方法中為每個輸出都創建Writable對象。例如,你的Wordcout mapper方法可能這樣寫:
Java代碼
public void map(...) {
…
for (String word : words) {
output.collect(new Text(word), new IntWritable(1));
}
}
這樣會導致程序分配出成千上萬個短周期的對象。Java垃圾收集器就要為此做很多的工作。更有效的寫法是:
Java代碼
class MyMapper … {
Text wordText = new Text();
IntWritable one = new IntWritable(1);
public void map(...) {
for (String word: words) {
wordText.set(word);
output.collect(wordText, one);
}
}
}
基準測試:
當我以上面的描述修改了Wordcount例子后,起初我發現job運行時與修改之前沒有任何不同。這是因為在我的cluster中默認為每個task都分配一個1GB的堆大小 ,所以垃圾回收機制沒有啟動。當我重新設置參數,為每個task只分配200MB的堆時,沒有重用Writable對象的這個版本執行出現了很嚴重的減緩 —job的執行時間由以前的大概8分30秒變成現在的超過17分鐘。原始的那個重用Writable的版本,在設置更小的堆時還是保持相同的執行速度。因此重用Writable是一個很簡單的問題修正,我推薦大家總是這樣做。它可能不會在每個job的執行中獲得很好的性能,但當你的task有內存限制時就會有相當大的區別。
第七點 使用簡易的剖析方式查看task的運行
這是我在查看MapReduce job性能問題時常用的一個小技巧。那些不希望這些做的人就會反對說這樣是行不通的,但是事實是擺在面前。
為了實現簡易的剖析,可以當job中一些task運行很慢時,用ssh工具連接上task所在的那臺task tracker機器。執行5到10次這個簡單的命令 sudo killall -QUIT java(每次執行間隔幾秒)。別擔心,不要被命令的名字嚇著,它不會導致任何東西退出。然后使用JobTracker的界面跳轉到那臺機器上某個task的stdout 文件上,或者查看正在運行的機器上/var/log/hadoop/userlogs/目錄中那個task的stdout文件。你就可以看到當你執行那段命令時,命令發送到JVM的SIGQUIT信號而產生的棧追蹤信息的dump文件。([譯]在JobTracker的界面上有Cluster Summary的表格,進入Nodes鏈接,選中你執行上面命令的server,在界面的最下方有Local Logs,點擊LOG進入,然后選擇userlogs目錄,這里可以看到以server執行過的jobID命名的幾個目錄,不管進入哪個目錄都可以看到很多task的列表,每個task的log中有個stdout文件,如果這個文件不為空,那么這個文件就是作者所說的棧信息文件)
解析處理這個輸出文件需要一點點以經驗,這里我介紹下平時是怎樣處理的:
對于棧信息中的每個線程,很快地查找你的java包的名字(假如是com.mycompany.mrjobs)。如果你當前線程的棧信息中沒有找到任何與你的代碼有關的信息,那么跳到另外的線程再看。
如果你在某些棧信息中看到你查找的代碼,很快地查閱并大概記下它在做什么事。假如你看到一些與NumberFormat相關的信息,那么此時你需要記下它,暫時不需要關注它是代碼的哪些行。
轉到日志中的下一個dump,然后也花一些時間做類似的事情然后記下些你關注的內容。
在查閱了4到5個棧信息后,你可能會意識到在每次查閱時都會有一些似曾相識的東西。如果這些你意識到的問題是阻礙你的程序變快的原因,那么你可能就找到了程序真正的問題。假如你取到10個線程的棧信息,然后從5個里面看到過NumberFormat類似的信息,那么可能意味著你將50%的CPU浪費在數據格式轉換的事情上了。
當然,這沒有你使用真正的分析程序那么科學。但我發現這是一種有效的方法,可以在不需要引入其它工具的時候發現那些明顯的CPU瓶頸。更重要的是,這是一種讓你會變的更強的技術,你會在實踐中知道一個正常的和有問題的dump是啥樣子。
通過這項技術我發現了一些通常出現在性能調優方面的誤解,列出在下面。
1. NumberFormat 相當慢,盡量避免使用它。
2. String.split—不管是編碼或是解碼UTF8的字符串都是慢的超出你的想像— 參照上面提到的建議,使用合適的Writable類型。
3. 使用StringBuffer.append來連接字符串
“提高MapReduce性能的方法有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。