Hadoop MapReduce開發最佳實踐

來源:互聯網
上載者:User
前言

本文是Hadoop最佳實踐系列第二篇,上一篇為《Hadoop管理員的十個最佳實踐》。

MapRuduce開發對於大多數程式師都會覺得略顯複雜,運行一個WordCount(Hadoop中hello word程式)不僅要熟悉MapRuduce模型,還要瞭解Linux命令(儘管有Cygwin, 但在Windows下運行MapRuduce仍然很麻 煩),此外還要學習程式的打包、部署、提交job、調試等技能,這足以讓很多學習者望而退步。

所以如何提高MapReduce開發效率便成了大家很關注的問題。 但Hadoop的Committer早已經考慮到這些問題,從而開發了 ToolRunner、MRunit(MapReduce最佳實踐第二篇中會介紹)、MiniMRCluster、MiniDFSCluster等輔助工 具, 説明解決開發、部署等問題。 舉一個自己親身的例子:

某週一和搭檔(結對程式設計)決定重構一個完成近10項統計工作的MapRuduce程式,這個MapReduce(從Spring專案移植過來的), 因為依賴Spring框架(原生Spring,非Spring Hadoop框架), 導致性能難以忍受,我們決定將Spring從程式中剔除。 重構之前程式運行是正確的,所以我們要保障重構後運行結果與重構前一致。 搭 檔說,為什麼我們不用TDD來完成這個事情呢? 於是我們研究並應用了MRunit,令人意想不到的是,重構工作只用了一天就完成,剩下一天我們進行用 findbug掃描了代碼,進行了集成測試。 這次重構工作我們沒有給程式帶來任何錯誤,不但如此我們還擁有了可靠的測試和更加穩固的代碼。 這件事情讓我們 很爽的同時,也在思考關於MapReduce開發效率的問題,要知道這次重構我們之前評估的時間是一周,我把這個事情分享到EasyHadoop群裡,大 家很有興趣,一個朋友問到,你們的評估太不准確了, 為什麼開始不評估2天完成呢? 我說如果我們沒有使用MRUnit,真的是需要一周才能完成。 因為有它單 元測試,我可以在5秒內得到我本次修改的回饋,否則至少需要10分鐘(編譯、打包、部署、提交MapReduce、人工驗證結果正確性),而且重構是個反 複修改,反復運行,得到回饋,再修改、再運行、再回饋的過程, MRunit在這裡幫了大忙。

相同智商、相同工作經驗的開發人員,借助有效的工具和方法,竟然可以帶來如此大的開發效率差距,不得不讓人驚詫!

PS. 本文基於Hadoop 1.0(Cloudera CDH3uX)。 本文適合讀者:Hadoop初級、中級開發者。

1. 使用ToolRunner讓參數傳遞更簡單

關於MapReduce運行和參數配置,你是否有下面的煩惱:

將MapReduce Job配置參數寫到java代碼裡,一旦變更意味著修改java檔源碼、編譯、打包、部署一連串事情。 當MapReduce 依賴設定檔的時候,你需要手工編寫java代碼使用DistributedCache將其上傳到HDFS中,以便map和reduce函數可以讀取。 當你的map或reduce 函數依賴協力廠商jar檔時,你在命令列中使用」-libjars」參數指定依賴jar包時,但根本沒生效。

其實,Hadoop有個ToolRunner類,它是個好東西,簡單好用。 無論在《Hadoop權威指南》還是Hadoop專案源碼自帶的example,都推薦使用ToolRunner。

下面我們看下src/example目錄下WordCount.java檔,它的代碼結構是這樣的:

public class WordCount { // 略... public static void main(String[] args) throws Exception { Configuration conf = new C onfiguration(); String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs(); 略... Job job = new Job(conf, "word count"); 略... System.exit(job.waitForCompletion(true) ? 0 : 1); }}

WordCount.java中使用到了GenericOptionsParser這個類,它的作用是將命令列中參數自動設置到變數conf中。 舉個例子,比如我希望通過命令列設置reduce task數量,就這麼寫:

    bin/hadoop jar MyJob.jar com.xxx.MyJobDriver -Dmapred.reduce.tasks=5

上面這樣就可以了,不需要將其硬編碼到java代碼中,很輕鬆就可以將參數與代碼分離開。

其它常用的參數還有」-libjars」和-「files」,使用方法一起送上:

bin/hadoop jar MyJob.jar com.xxx.MyJobDriver -Dmapred.reduce.tasks=5 \ -files ./dict.conf \ -libjars lib/commons-bea nutils-1.8.3.jar,lib/commons-digester-2.1.jar

參數」-libjars」的作用是上傳本地jar包到HDFS中MapReduce臨時目錄並將其設置到map和reduce task的classpath中;參數」-files」 的作用是上傳指定檔到HDFS中mapreduce臨時目錄,並允許map和reduce task讀取到它。 這兩個配置參數其實都是通過DistributeCache來實現的。

至此,我們還沒有說到ToolRunner,上面的代碼我們使用了GenericOptionsParser幫我們解析命令列參數,編寫 ToolRunner的程式師更懶,它將 GenericOptionsParser調用隱藏到自身run方法,被自動執行了,修改後的代碼變成了這樣:

public class WordCount extends Configured implements Tool { @Override public int run(String[] arg0) throws Exception { Job job = new Job(getConf(), "word count"); 略... System.exit(job.waitForCompletion(true) ? 0 : 1); return 0; } public static void main(String[] args) throws Exception { int res = ToolRunner.run(new Configuration(), new WordCount(), args); System.exit(res); }}

看看代碼上有什麼不同:

讓WordCount繼承Configured並實現Tool介面。 重寫Tool介面的run方法,run方法不是static類型,這很好。 在WordCount中我們將通過getConf()獲取Configuration物件。

關於GenericOptionsParser更多用法,請點擊這裡:GenericOptionsParser.html

推薦指數:★★★★

推薦理由:通過簡單的幾步,就可以實現代碼與配置隔離、上傳檔到DistributeCache等功能。 修改MapReduce參數不需要修改java代碼、打包、部署,提高工作效率。

2. 有效使用Hadoop源碼

作為MapReduce程式師不可避免的要使用Hadoop源碼,Why? 記得2010剛接觸hadoop的時候,總是搞不清舊api和新api的 使用方法。 寫了一段程式,在一個新api裡面調用某個方法每次都是返回Null,非常惱火,後來附上源碼發現,這個方法真的就是只做了「return null」並沒有給予實現,最後只得想其它方法曲線救國。 總之要想真正瞭解MapReduce開發,源碼是不可缺少的工具。

下面是我的源碼使用實踐,步驟有點麻煩不過配置一次就好:

1. Eclipse中創建Hadoop源碼專案

1.1 下載並解壓縮Hadoop分發包(通常是tar.gz包)

1.2 Eclipse中新建JAVA專案

1.3 將解壓後hadoop源碼包/src目錄中core, hdfs, mapred, tool幾個目錄(其它幾個源碼根據需要進行選擇)copy到eclipse新建專案的src目錄。

1.4 右鍵點擊eclipse專案,選擇「Properties」,在彈出對話方塊中左邊功能表選擇「JAVA Build Path」:
a) 點擊「Source」標籤。 先刪除src這個目錄,然後依次添加剛才copy過來的目錄
b) 點擊當前對話方塊「Libaries」,點擊「Add External JARs」,在快顯視窗中添加$HADOOPHOME下幾個hadoop程式jar包,然後再次添加$HADOOPHOME /lib、$HADOOP_HOME / lib/jsp-2.1兩個目錄下所有jar包,最後還要添加ANT專案lib目錄下ant.jar檔。

1.5 此時源碼專案應該只有關於找不到sun.security包的錯誤了。 這時我們還是在「Libraries」這個標籤中,展開jar包清單最低下的 「JRE System Library」,按兩下」Access rules」,在快顯視窗中點擊「add按鈕」,然後在新對話方塊中"Resolution" 下拉清單選擇"Accessible","Rule Pattern"填寫*/,保存後就OK了。 如下圖:

2. 如何使用這個源碼專案呢?

比如我知道Hadoop某個源碼檔的名稱,在eclipse中可以通過快速鍵「Ctrl + Shift + R」調出查找視窗,輸入檔案名,如「MapTask」,那可以打開這個類的源碼了。

還有個使用場景,當我們編寫MapReduce程式的時候,我想直接打開某個類的源碼,通過上面的操作還是有點麻煩,比如我想看看Job類是如何實現的,當我點擊它的時候會出現下面的情景:

解決辦法很簡單:

點擊圖中「Attach Source」按鈕-> 點擊「Workspace」按鈕->選擇剛才新建的Hadoop源碼專案。 完成後源碼應該就蹦出來了。

總結一下,本實踐中我們獲得了什麼功能:

知道hadoop源碼檔案名,快速找到該檔寫程式的時候直接查看Hadoop相關類源碼Debug程式的時候,可以直接進入源碼查看並跟蹤運行

推薦指數:★★★★

推薦理由:通過源碼可以説明我們更深入瞭解Hadoop,可以説明我們解決複雜問題

3. 正確使用壓縮演算法

下表資料引用cloudera官方網站的一篇博客,原文點這裡。

CompressionFileSize(GB)Compression Time (s)Decompression Time (s)Nonesome_logs8.0--Gzipsome_ logs.gz1.324172LZOsome_logs.lzo2.05535

上面表格與筆者集群實際環境測試結果一致,所以我們可以得出如下結論:

LZO檔的壓縮和解壓縮性能要遠遠好于Gzip檔。 相同文字檔,使用Gzip壓縮可以比LZO壓縮大幅減少磁碟空間。

上面的結論對我們有什麼説明呢? 在合適的環節使用合適壓縮演算法。

在中國的頻寬成本是非常貴的,費用上要遠遠高於美國、韓國等國家。 所以在資料傳輸環節,我們希望使用了Gzip演算法壓縮檔,目的是減少檔案傳輸 量,降低頻寬成本。 使用LZO檔作為MapReduce檔的輸入(創建lzo index後是支援自動分片輸入的)。 對於大檔,一個map task的輸入將變為一個block,而不是像Gzip檔一樣讀取整個檔,這將大幅提升MapReduce運行效率。

主流傳輸工具FlumeNG和scribe預設都是非壓縮傳輸的(都是通過一行日誌一個event進行控制的),這點大家在使用時要注意。 FlumeNG可以自訂群組件方式實現一次傳輸多條壓縮資料,然後接收端解壓縮的方式來實現資料壓縮傳輸,scribe沒有使用過不評論。

另外值得一提的就是snappy,它是由Google開發並開源的壓縮演算法的,是Cloudera官方大力提倡在MapReduce中使用的壓縮算 法。 它的特點是:與LZO檔相近的壓縮率的情況下,還可以大幅提升壓縮和解壓縮性能,但是它作為MapReduce輸入是不可以分割的。

延伸內容:

Cloudera官方Blog對Snappy介紹:

HTTP://blog.cloudera.com/blog/2011/09/snappy-and-hadoop/

老外上傳的壓縮演算法效能測試資料:

HTTP://pastebin.com/SFaNzRuf

推薦指數:★★★★★

推薦理由:壓縮率和壓縮性能一定程度是矛盾體,如何均衡取決於應用場景。 使用合適壓縮演算法直接關係到老闆的錢,如果能夠節省成本,體現程式師的價值。

4. 在合適的時候使用Combiner

map和 reduce 函數的輸入輸出都是key-value,Combiner和它們是一樣的。 作為map和reduce的中間環節,它的作用是聚合map task的磁片,減少map端磁片寫入,減少reduce端處理的資料量,對於有大量shuffle的job來說,性能往往取決於reduce端。 因為 reduce 端要經過從map端copy資料、reduce端歸併排序,最後才是執行reduce方法,此時如果可以減少map task輸出將對整個job帶來非常大的影響。

什麼時候可以使用Combiner?

比如你的Job是WordCount,那麼完全可以通過Combiner對map 函數輸出資料先進行聚合,然後再將Combiner輸出的結果發送到reduce端。

什麼時候不能使用Combiner?

WordCount在reduce端做的是加法,如果我們reduce需求是計算一大堆數位的平均數,則要求reduce獲取到全部的數位進行計 算,才可以得到正確值。 此時,是不能使用Combiner的,因為會其會影響最終結果。 注意事項:即使設置Combiner,它也不一定被執行(受參數min.num.spills.for.combine影響),所以使用Combiner 的場景應保證即使沒有Combiner,我們的MapReduce也能正常運行。

推薦指數:★★★★★

推薦理由:在合適的場景使用Combiner,可以大幅提升MapReduce 性能。

5. 通過回檔通知知道MapReduce什麼時候完成

你知道什麼時候MapReduce完成嗎? 知道它執行成功或是失敗嗎?

Hadoop包含job通知這個功能,要使用它非常容易,借助我們實踐一的ToolRunner,在命令列裡面就可以進行設置,下面是一個例子:

hadoop jar MyJob.jar com.xxx.MyJobDriver \-Djob.end.notification.url=HTTP://moniter/mapred_notify/\$jobId/\$ jobStatus

通過上面的參數設置後,當MapReduce完成後將會回檔我參數中的介面。 其中$jobId和$jobStatus會自動被實際值代替。

上面在$jobId和$jobStatus兩個變數前,我添加了shell中的轉義符」\」,如果使用java代碼設置該參數是不需要轉義符的。

總結下:看看我們通過該實踐可以獲得什麼?

獲取MapReduce執行時間和回檔完成時間,可以分析最耗時Job,最快完成Job。 通過MapReduce運行狀態(包括成功、失敗、Kill),可以第一時間發現錯誤,並通知運維。 通過獲取MapReduce完成時間,可以第一時間通過使用者,資料已經計算完成,提升使用者體驗

Hadoop這塊功能的源碼檔是JobEndNotifier.java,可以馬上通過本文實踐二看看究竟。 其中下面兩個參數就是我通過翻源碼的時候發現的,如果希望使用該實踐趕緊通過ToolRunner設置上吧(別忘了加-D,格式是-Dkey=value)。

job.end.retry.attempts // 設置回檔通知retry次數job.end.retry.interval // 設置回檔時間間隔,單位毫秒

當然如果hadoop沒有提供Job狀態通知的功能,我們也可以通過採用阻塞模式提交MapReduce Job,然後Job完成後也可以獲知其狀態和執行時間。

推薦指數:★★★

推薦理由:對mapreduce job監控最省事有效的辦法,沒有之一。

作者介紹:

張月,EasyHadoop技術社區志願者,JAVA程式師,7年工作經驗。 2007年加入藍汛ChinaCache至今,目前從事Hadoop相關工作。 關注敏捷和海量資料領域,關注效率。 博客:heipark.iteye.com,微博:@張月_痛苦的信仰。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.