hadoop 併發編程

轉自 http://www.ibm.com/developerworks/cn/opensource/os-cn-hadoop2/index.html 

分析 WordCount 程序

我們先來看看 Hadoop 自帶的示例程序 WordCount,這個程序用於統計一批文本文件中單詞出現的頻率,完整的代碼可在下載的 Hadoop 安裝包中得到(在 src/examples 目錄中)。

1.實現Map類

見代碼清單1。這個類實現 Mapper 接口中的 map 方法,輸入參數中的 value 是文本文件中的一行,利用 StringTokenizer 將這個字符串拆成單詞,然後將輸出結果 <單詞,1> 寫入到 org.apache.hadoop.mapred.OutputCollector 中。OutputCollector 由 Hadoop 框架提供, 負責收集 Mapper 和 Reducer 的輸出數據,實現 map 函數和 reduce 函數時,只需要簡單地將其輸出的 <key,value> 對往 OutputCollector 中一丟即可,剩餘的事框架自會幫你處理好。

代碼中 LongWritable, IntWritable, Text 均是 Hadoop 中實現的用於封裝 Java 數據類型的類,這些類都能夠被串行化從而便於在分佈式環境中進行數據交換,你可以將它們分別視爲 long, int, String 的替代品。Reporter 則可用於報告整個應用的運行進度,本例中未使用。


代碼清單1
                
public static class MapClass extends MapReduceBase
    implements Mapper<LongWritable, Text, Text, IntWritable>{
    
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();
    
    public void map(LongWritable key, Text value, 
                    OutputCollector<Text, IntWritable> output, 
                    Reporter reporter) throws IOException {
      String line = value.toString();
      StringTokenizer itr = new StringTokenizer(line);
      while (itr.hasMoreTokens()) {
        word.set(itr.nextToken());
        output.collect(word, one);
      }
    }
  }

2.實現 Reduce 類

見代碼清單 2。這個類實現 Reducer 接口中的 reduce 方法, 輸入參數中的 key, values 是由 Map 任務輸出的中間結果,values 是一個 Iterator, 遍歷這個 Iterator, 就可以得到屬於同一個 key 的所有 value. 此處,key 是一個單詞,value 是詞頻。只需要將所有的 value 相加,就可以得到這個單詞的總的出現次數。


代碼清單 2
                
public static class Reduce extends MapReduceBase
    implements Reducer<Text, IntWritable, Text, IntWritable> {
    
    public void reduce(Text key, Iterator<IntWritable> values,
                       OutputCollector<Text, IntWritable> output, 
                       Reporter reporter) throws IOException {
      int sum = 0;
      while (values.hasNext()) {
        sum += values.next().get();
      }
      output.collect(key, new IntWritable(sum));
    }
  }

3.運行 Job

在 Hadoop 中一次計算任務稱之爲一個 job, 可以通過一個 JobConf 對象設置如何運行這個 job。此處定義了輸出的 key 的類型是 Text, value 的類型是 IntWritable, 指定使用代碼清單1中實現的 MapClass 作爲 Mapper 類, 使用代碼清單2中實現的 Reduce 作爲 Reducer 類和 Combiner 類, 任務的輸入路徑和輸出路徑由命令行參數指定,這樣 job 運行時會處理輸入路徑下的所有文件,並將計算結果寫到輸出路徑下。

然後將 JobConf 對象作爲參數,調用 JobClient 的 runJob, 開始執行這個計算任務。至於 main 方法中使用的 ToolRunner 是一個運行 MapReduce 任務的輔助工具類,依樣畫葫蘆用之即可。


代碼清單 3
                
 public int run(String[] args) throws Exception {
    JobConf conf = new JobConf(getConf(), WordCount.class);
    conf.setJobName("wordcount");
    
    conf.setOutputKeyClass(Text.class);
    conf.setOutputValueClass(IntWritable.class);
    
    conf.setMapperClass(MapClass.class);        
    conf.setCombinerClass(Reduce.class);
    conf.setReducerClass(Reduce.class);
     
    conf.setInputPath(new Path(args[0]));
    conf.setOutputPath(new Path(args[1]));
        
    JobClient.runJob(conf);
    return 0;
  }
  
public static void main(String[] args) throws Exception {
    if(args.length != 2){
      System.err.println("Usage: WordCount <input path> <output path>");
      System.exit(-1);
    }
    int res = ToolRunner.run(new Configuration(), new WordCount(), args);
    System.exit(res);
  }
}

以上就是 WordCount 程序的全部細節,簡單到讓人吃驚,您都不敢相信就這麼幾行代碼就可以分佈式運行於大規模集羣上,並行處理海量數據集。

4. 通過 JobConf 定製計算任務

通過上文所述的 JobConf 對象,程序員可以設定各種參數,定製如何完成一個計算任務。這些參數很多情況下就是一個 java 接口,通過注入這些接口的特定實現,可以定義一個計算任務( job )的全部細節。瞭解這些參數及其缺省設置,您才能在編寫自己的並行計算程序時做到輕車熟路,遊刃有餘,明白哪些類是需要自己實現的,哪些類用 Hadoop 的缺省實現即可。表一是對 JobConf 對象中可以設置的一些重要參數的總結和說明,表中第一列中的參數在 JobConf 中均會有相應的 get/set 方法,對程序員來說,只有在表中第三列中的缺省值無法滿足您的需求時,才需要調用這些 set 方法,設定合適的參數值,實現自己的計算目的。針對表格中第一列中的接口,除了第三列的缺省實現之外,Hadoop 通常還會有一些其它的實現,我在表格第四列中列出了部分,您可以查閱 Hadoop 的 API 文檔或源代碼獲得更詳細的信息,在很多的情況下,您都不用實現自己的 Mapper 和 Reducer, 直接使用 Hadoop 自帶的一些實現即可。


表一 JobConf 常用可定製參數
參數 作用 缺省值 其它實現
InputFormat 將輸入的數據集切割成小數據集 InputSplits, 每一個 InputSplit 將由一個 Mapper 負責處理。此外 InputFormat 中還提供一個 RecordReader 的實現, 將一個 InputSplit 解析成 <key,value> 對提供給 map 函數。 TextInputFormat
(針對文本文件,按行將文本文件切割成 InputSplits, 並用 LineRecordReader 將 InputSplit 解析成 <key,value> 對,key 是行在文件中的位置,value 是文件中的一行)
SequenceFileInputFormat
OutputFormat 提供一個 RecordWriter 的實現,負責輸出最終結果 TextOutputFormat
(用 LineRecordWriter 將最終結果寫成純文件文件,每個 <key,value> 對一行,key 和 value 之間用 tab 分隔)
SequenceFileOutputFormat
OutputKeyClass 輸出的最終結果中 key 的類型 LongWritable  
OutputValueClass 輸出的最終結果中 value 的類型 Text  
MapperClass Mapper 類,實現 map 函數,完成輸入的 <key,value> 到中間結果的映射 IdentityMapper
(將輸入的 <key,value> 原封不動的輸出爲中間結果)
LongSumReducer,
LogRegexMapper,
InverseMapper
CombinerClass 實現 combine 函數,將中間結果中的重複 key 做合併 null
(不對中間結果中的重複 key 做合併)
 
ReducerClass Reducer 類,實現 reduce 函數,對中間結果做合併,形成最終結果 IdentityReducer
(將中間結果直接輸出爲最終結果)
AccumulatingReducer, LongSumReducer
InputPath 設定 job 的輸入目錄, job 運行時會處理輸入目錄下的所有文件 null  
OutputPath 設定 job 的輸出目錄,job 的最終結果會寫入輸出目錄下 null  
MapOutputKeyClass 設定 map 函數輸出的中間結果中 key 的類型 如果用戶沒有設定的話,使用 OutputKeyClass  
MapOutputValueClass 設定 map 函數輸出的中間結果中 value 的類型 如果用戶沒有設定的話,使用 OutputValuesClass  
OutputKeyComparator 對結果中的 key 進行排序時的使用的比較器 WritableComparable  
PartitionerClass 對中間結果的 key 排序後,用此 Partition 函數將其劃分爲R份,每份由一個 Reducer 負責處理。 HashPartitioner
(使用 Hash 函數做 partition)
KeyFieldBasedPartitioner PipesPartitioner

改進的 WordCount 程序

現在你對 Hadoop 並行程序的細節已經有了比較深入的瞭解,我們來把 WordCount 程序改進一下,目標: (1)原 WordCount 程序僅按空格切分單詞,導致各類標點符號與單詞混雜在一起,改進後的程序應該能夠正確的切出單詞,並且單詞不要區分大小寫。(2)在最終結果中,按單詞出現頻率的降序進行排序。

1.修改 Mapper 類,實現目標(1)

實現很簡單,見代碼清單4中的註釋。


代碼清單 4
                
public static class MapClass extends MapReduceBase
    implements Mapper<LongWritable, Text, Text, IntWritable> {
    
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();
    private String pattern="[^\\w]"; //正則表達式,代表不是0-9, a-z, A-Z的所有其它字符
    
    public void map(LongWritable key, Text value, 
                    OutputCollector<Text, IntWritable> output, 
                    Reporter reporter) throws IOException {
      String line = value.toString().toLowerCase(); //全部轉爲小寫字母
      line = line.replaceAll(pattern, " "); //將非0-9, a-z, A-Z的字符替換爲空格
      StringTokenizer itr = new StringTokenizer(line);
      while (itr.hasMoreTokens()) {
        word.set(itr.nextToken());
        output.collect(word, one);
      }
    }
  }

2.實現目標(2)

用一個並行計算任務顯然是無法同時完成單詞詞頻統計和排序的,這時我們可以利用 Hadoop 的任務管道能力,用上一個任務(詞頻統計)的輸出做爲下一個任務(排序)的輸入,順序執行兩個並行計算任務。主要工作是修改代碼清單3中的 run 函數,在其中定義一個排序任務並運行之。

在 Hadoop 中要實現排序是很簡單的,因爲在 MapReduce 的過程中,會把中間結果根據 key 排序並按 key 切成 R 份交給 R 個 Reduce 函數,而 Reduce 函數在處理中間結果之前也會有一個按 key 進行排序的過程,故 MapReduce 輸出的最終結果實際上已經按 key 排好序。詞頻統計任務輸出的 key 是單詞,value 是詞頻,爲了實現按詞頻排序,我們指定使用 InverseMapper 類作爲排序任務的 Mapper 類( sortJob.setMapperClass(InverseMapper.class );),這個類的 map 函數簡單地將輸入的 key 和 value 互換後作爲中間結果輸出,在本例中即是將詞頻作爲 key,單詞作爲 value 輸出, 這樣自然就能得到按詞頻排好序的最終結果。我們無需指定 Reduce 類,Hadoop 會使用缺省的 IdentityReducer 類,將中間結果原樣輸出。

還有一個問題需要解決: 排序任務中的 Key 的類型是 IntWritable, (sortJob.setOutputKeyClass(IntWritable.class)), Hadoop 默認對 IntWritable 按升序排序,而我們需要的是按降序排列。因此我們實現了一個 IntWritableDecreasingComparator 類, 並指定使用這個自定義的 Comparator 類對輸出結果中的 key (詞頻)進行排序:sortJob.setOutputKeyComparatorClass(IntWritableDecreasingComparator.class)

詳見代碼清單 5 及其中的註釋。


代碼清單 5
                
public int run(String[] args) throws Exception {
        Path tempDir = new Path("wordcount-temp-" + Integer.toString(
            new Random().nextInt(Integer.MAX_VALUE))); //定義一個臨時目錄

        JobConf conf = new JobConf(getConf(), WordCount.class);
        try {
            conf.setJobName("wordcount");

            conf.setOutputKeyClass(Text.class);
            conf.setOutputValueClass(IntWritable.class);

            conf.setMapperClass(MapClass.class);
            conf.setCombinerClass(Reduce.class);
            conf.setReducerClass(Reduce.class);

            conf.setInputPath(new Path(args[0]));
            conf.setOutputPath(tempDir); //先將詞頻統計任務的輸出結果寫到臨時目
                                         //錄中, 下一個排序任務以臨時目錄爲輸入目錄。
            
            conf.setOutputFormat(SequenceFileOutputFormat.class);
            
            JobClient.runJob(conf);

            JobConf sortJob = new JobConf(getConf(), WordCount.class);
            sortJob.setJobName("sort");

            sortJob.setInputPath(tempDir);
            sortJob.setInputFormat(SequenceFileInputFormat.class);

            sortJob.setMapperClass(InverseMapper.class);

            sortJob.setNumReduceTasks(1); //將 Reducer 的個數限定爲1, 最終輸出的結果
                                //文件就是一個。
            sortJob.setOutputPath(new Path(args[1]));
            sortJob.setOutputKeyClass(IntWritable.class);
            sortJob.setOutputValueClass(Text.class);
                  
            sortJob.setOutputKeyComparatorClass(IntWritableDecreasingComparator.class);

            JobClient.runJob(sortJob);
        } finally {
            FileSystem.get(conf).delete(tempDir); //刪除臨時目錄
        }
    return 0;
  }
  
  private static class IntWritableDecreasingComparator extends IntWritable.Comparator {
      public int compare(WritableComparable a, WritableComparable b) {
        return -super.compare(a, b);
      }
      
      public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
          return -super.compare(b1, s1, l1, b2, s2, l2);
      }
  }



在 Eclipse 環境下進行開發和調試

在 Eclipse 環境下可以方便地進行 Hadoop 並行程序的開發和調試。推薦使用 IBM MapReduce Tools for Eclipse, 使用這個 Eclipse plugin 可以簡化開發和部署 Hadoop 並行程序的過程。基於這個 plugin, 可以在 Eclipse 中創建一個 Hadoop MapReduce 應用程序,並且提供了一些基於 MapReduce 框架的類開發的嚮導,可以打包成 JAR 文件,部署一個 Hadoop MapReduce 應用程序到一個 Hadoop 服務器(本地和遠程均可),可以通過一個專門的視圖 ( perspective ) 查看 Hadoop 服務器、Hadoop 分佈式文件系統( DFS )和當前運行的任務的狀態。

可在 IBM alphaWorks 網站下載這個 MapReduce Tool, 或在本文的下載清單中下載。將下載後的壓縮包解壓到你 Eclipse 安裝目錄,重新啓動 Eclipse 即可使用了。

設置 Hadoop 主目錄

點擊 Eclipse 主菜單上 Windows->Preferences, 然後在左側選擇 Hadoop Home Directory,設定你的 Hadoop 主目錄,如圖一所示:


圖 1
圖 1

創立一個 MapReduce Project

點擊 Eclipse 主菜單上 File->New->Project, 在彈出的對話框中選擇 MapReduce Project, 輸入 project name 如 wordcount, 然後點擊 Finish 即可。,如圖 2 所示:


圖 2
圖 2

此後,你就可以象一個普通的 Eclipse Java project 那樣,添加入 Java 類,比如你可以定義一個 WordCount 類,然後將本文代碼清單1,2,3中的代碼寫到此類中,添加入必要的 import 語句 ( Eclipse 快捷鍵 ctrl+shift+o 可以幫你),即可形成一個完整的 wordcount 程序。

在我們這個簡單的 wordcount 程序中,我們把全部的內容都放在一個 WordCount 類中。實際上 IBM MapReduce tools 還提供了幾個實用的嚮導 ( wizard ) 工具,幫你創建單獨的 Mapper 類,Reducer 類,MapReduce Driver 類(就是代碼清單3中那部分內容),在編寫比較複雜的 MapReduce 程序時,將這些類獨立出來是非常有必要的,也有利於在不同的計算任務中重用你編寫的各種 Mapper 類和 Reducer 類。

在 Eclipse 中運行

如圖三所示,設定程序的運行參數:輸入目錄和輸出目錄之後,你就可以在 Eclipse 中運行 wordcount 程序了,當然,你也可以設定斷點,調試程序。


圖 3
圖 3

結束語

到目前爲止,我們已經介紹了 MapReduce 計算模型,分佈式文件系統 HDFS,分佈式並行計算等的基本原理, 如何安裝和部署單機 Hadoop 環境,實際編寫了一個 Hadoop 並行計算程序,並瞭解了一些重要的編程細節,瞭解瞭如何使用 IBM MapReduce Tools 在 Eclipse 環境中編譯,運行和調試你的 Hadoop 並行計算程序。但一個 Hadoop 並行計算程序,只有部署運行在分佈式集羣環境中,才能發揮其真正的優勢,在這篇系列文章的第 3 部分中,你將瞭解到如何部署你的分佈式 Hadoop 環境,如何利用 IBM MapReduce Tools 將你的程序部署到分佈式環境中運行等內容。

聲明:本文僅代表作者個人之觀點,不代表 IBM 公司之觀點。



下載

描述 名字 大小 下載方法
改進的 wordcount 程序 wordcount.zip 8KB HTTP
IBM MapReduce Tools mapreduce_plugin.zip 324KB HTTP

關於下載方法的信息


參考資料

學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章