Hadoop 三劍客之 —— 分散式計算框架 MapReduce

單人影發表於2019-06-27

一、MapReduce概述

Hadoop MapReduce是一個分散式計算框架,用於編寫批處理應用程式。編寫好的程式可以提交到Hadoop叢集上用於並行處理大規模的資料集。

MapReduce作業通過將輸入的資料集拆分為獨立的塊,這些塊由map以並行的方式處理,框架對map的輸出進行排序,然後輸入到reduce中。MapReduce框架專門用於<key,value>鍵值對處理,它將作業的輸入視為一組<key,value>對,並生成一組<key,value>對作為輸出。輸出和輸出的keyvalue都必須實現Writable 介面。

(input) <k1, v1> -> map -> <k2, v2> -> combine -> <k2, v2> -> reduce -> <k3, v3> (output)

二、MapReduce程式設計模型簡述

這裡以詞頻統計為例進行說明,MapReduce處理的流程如下:

Hadoop 三劍客之 —— 分散式計算框架 MapReduce
  1. input : 讀取文字檔案;
  2. splitting : 將檔案按照行進行拆分,此時得到的K1行數,V1表示對應行的文字內容;
  3. mapping : 並行將每一行按照空格進行拆分,拆分得到的List(K2,V2),其中K2代表每一個單詞,由於是做詞頻統計,所以V2的值為1,代表出現1次;
  4. shuffling:由於Mapping操作可能是在不同的機器上並行處理的,所以需要通過shuffling將相同key值的資料分發到同一個節點上去合併,這樣才能統計出最終的結果,此時得到K2為每一個單詞,List(V2)為可迭代集合,V2就是Mapping中的V2;
  5. Reducing : 這裡的案例是統計單詞出現的總次數,所以ReducingList(V2)進行歸約求和操作,最終輸出。

MapReduce程式設計模型中splittingshuffing操作都是由框架實現的,需要我們自己程式設計實現的只有mappingreducing,這也就是MapReduce這個稱呼的來源。

三、combiner & partitioner

Hadoop 三劍客之 —— 分散式計算框架 MapReduce

3.1 InputFormat & RecordReaders

InputFormat將輸出檔案拆分為多個InputSplit,並由RecordReadersInputSplit轉換為標準的<key,value>鍵值對,作為map的輸出。這一步的意義在於只有先進行邏輯拆分並轉為標準的鍵值對格式後,才能為多個map提供輸入,以便進行並行處理。

3.2 Combiner

combinermap運算後的可選操作,它實際上是一個本地化的reduce操作,它主要是在map計算出中間檔案後做一個簡單的合併重複key值的操作。這裡以詞頻統計為例:

map在遇到一個hadoop的單詞時就會記錄為1,但是這篇文章裡hadoop可能會出現n多次,那麼map輸出檔案冗餘就會很多,因此在reduce計算前對相同的key做一個合併操作,那麼需要傳輸的資料量就會減少,傳輸效率就可以得到提升。

但並非所有場景都適合使用combiner,使用它的原則是combiner的輸出不會影響到reduce計算的最終輸入,例如:求總數,最大值,最小值時都可以使用combiner,但是做平均值計算則不能使用combiner

不使用combiner的情況:

Hadoop 三劍客之 —— 分散式計算框架 MapReduce

使用combiner的情況:

Hadoop 三劍客之 —— 分散式計算框架 MapReduce

可以看到使用combiner的時候,需要傳輸到reducer中的資料由12keys,降低到10keys。降低的幅度取決於你keys的重複率,下文詞頻統計案例會演示用combiner降低數百倍的傳輸量。

3.3 Partitioner

partitioner可以理解成分類器,將map的輸出按照key值的不同分別分給對應的reducer,支援自定義實現,下文案例會給出演示。

四、MapReduce詞頻統計案例

4.1 專案簡介

這裡給出一個經典的詞頻統計的案例:統計如下樣本資料中每個單詞出現的次數。

Spark   HBase
Hive    Flink   Storm   Hadoop  HBase   Spark
Flink
HBase   Storm
HBase   Hadoop  Hive    Flink
HBase   Flink   Hive    Storm
Hive    Flink   Hadoop
HBase   Hive
Hadoop  Spark   HBase   Storm
HBase   Hadoop  Hive    Flink
HBase   Flink   Hive    Storm
Hive    Flink   Hadoop
HBase   Hive

為方便大家開發,我在專案原始碼中放置了一個工具類WordCountDataUtils,用於模擬產生詞頻統計的樣本,生成的檔案支援輸出到本地或者直接寫到HDFS上。

專案完整原始碼下載地址:hadoop-word-count

4.2 專案依賴

想要進行MapReduce程式設計,需要匯入hadoop-client依賴:

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>${hadoop.version}</version>
</dependency>

4.3 WordCountMapper

將每行資料按照指定分隔符進行拆分。這裡需要注意在MapReduce中必須使用Hadoop定義的型別,因為Hadoop預定義的型別都是可序列化,可比較的,所有型別均實現了WritableComparable介面。

public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, 
                                                                      InterruptedException {
        String[] words = value.toString().split("\t");
        for (String word : words) {
            context.write(new Text(word), new IntWritable(1));
        }
    }

}

WordCountMapper對應下圖的Mapping操作:

Hadoop 三劍客之 —— 分散式計算框架 MapReduce

WordCountMapper繼承自Mappe類,這是一個泛型類,定義如下:

WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>

public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
   ......
}
  • KEYIN : mapping輸入key的型別,即每行的偏移量(每行第一個字元在整個文字中的位置),Long型別,對應Hadoop中的LongWritable型別;
  • VALUEIN : mapping輸入value的型別,即每行資料;String型別,對應Hadoop中Text型別;
  • KEYOUTmapping輸出的key的型別,即每個單詞;String型別,對應Hadoop中Text型別;
  • VALUEOUTmapping輸出value的型別,即每個單詞出現的次數;這裡用int型別,對應IntWritable型別。

4.4 WordCountReducer

在Reduce中進行單詞出現次數的統計:

public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, 
                                                                                  InterruptedException {
        int count = 0;
        for (IntWritable value : values) {
            count += value.get();
        }
        context.write(key, new IntWritable(count));
    }
}

如下圖,shuffling的輸出是reduce的輸入。這裡的key是每個單詞,values是一個可迭代的資料型別,類似(1,1,1,...)

Hadoop 三劍客之 —— 分散式計算框架 MapReduce

4.4 WordCountApp

組裝MapReduce作業,並提交到伺服器執行,程式碼如下:

/**
 * 組裝作業 並提交到叢集執行
 */
public class WordCountApp {


    // 這裡為了直觀顯示引數 使用了硬編碼,實際開發中可以通過外部傳參
    private static final String HDFS_URL = "hdfs://192.168.0.107:8020";
    private static final String HADOOP_USER_NAME = "root";

    public static void main(String[] args) throws Exception {

        //  檔案輸入路徑和輸出路徑由外部傳參指定
        if (args.length < 2) {
            System.out.println("Input and output paths are necessary!");
            return;
        }

        // 需要指明hadoop使用者名稱,否則在HDFS上建立目錄時可能會丟擲許可權不足的異常
        System.setProperty("HADOOP_USER_NAME", HADOOP_USER_NAME);

        Configuration configuration = new Configuration();
        // 指明HDFS的地址
        configuration.set("fs.defaultFS", HDFS_URL);

        // 建立一個Job
        Job job = Job.getInstance(configuration);

        // 設定執行的主類
        job.setJarByClass(WordCountApp.class);

        // 設定Mapper和Reducer
        job.setMapperClass(WordCountMapper.class);
        job.setReducerClass(WordCountReducer.class);

        // 設定Mapper輸出key和value的型別
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        // 設定Reducer輸出key和value的型別
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 如果輸出目錄已經存在,則必須先刪除,否則重複執行程式時會丟擲異常
        FileSystem fileSystem = FileSystem.get(new URI(HDFS_URL), configuration, HADOOP_USER_NAME);
        Path outputPath = new Path(args[1]);
        if (fileSystem.exists(outputPath)) {
            fileSystem.delete(outputPath, true);
        }

        // 設定作業輸入檔案和輸出檔案的路徑
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, outputPath);

        // 將作業提交到群集並等待它完成,引數設定為true代表列印顯示對應的進度
        boolean result = job.waitForCompletion(true);

        // 關閉之前建立的fileSystem
        fileSystem.close();

        // 根據作業結果,終止當前執行的Java虛擬機器,退出程式
        System.exit(result ? 0 : -1);

    }
}

需要注意的是:如果不設定Mapper操作的輸出型別,則程式預設它和Reducer操作輸出的型別相同。

4.5 提交到伺服器執行

在實際開發中,可以在本機配置hadoop開發環境,直接在IDE中啟動進行測試。這裡主要介紹一下打包提交到伺服器執行。由於本專案沒有使用除Hadoop外的第三方依賴,直接打包即可:

# mvn clean package

使用以下命令提交作業:

hadoop jar /usr/appjar/hadoop-word-count-1.0.jar \
com.heibaiying.WordCountApp \
/wordcount/input.txt /wordcount/output/WordCountApp

作業完成後檢視HDFS上生成目錄:

# 檢視目錄
hadoop fs -ls /wordcount/output/WordCountApp

# 檢視統計結果
hadoop fs -cat /wordcount/output/WordCountApp/part-r-00000
Hadoop 三劍客之 —— 分散式計算框架 MapReduce

五、詞頻統計案例進階之Combiner

5.1 程式碼實現

想要使用combiner功能只要在組裝作業時,新增下面一行程式碼即可:

// 設定Combiner
job.setCombinerClass(WordCountReducer.class);

5.2 執行結果

加入combiner後統計結果是不會有變化的,但是可以從列印的日誌看出combiner的效果:

沒有加入combiner的列印日誌:

Hadoop 三劍客之 —— 分散式計算框架 MapReduce

加入combiner後的列印日誌如下:

Hadoop 三劍客之 —— 分散式計算框架 MapReduce

這裡我們只有一個輸入檔案並且小於128M,所以只有一個Map進行處理。可以看到經過combiner後,records由3519降低為6(樣本中單詞種類就只有6種),在這個用例中combiner就能極大地降低需要傳輸的資料量。

六、詞頻統計案例進階之Partitioner

6.1 預設的Partitioner

這裡假設有個需求:將不同單詞的統計結果輸出到不同檔案。這種需求實際上比較常見,比如統計產品的銷量時,需要將結果按照產品種類進行拆分。要實現這個功能,就需要用到自定義Partitioner

這裡先介紹下MapReduce預設的分類規則:在構建job時候,如果不指定,預設的使用的是HashPartitioner:對key值進行雜湊雜湊並對numReduceTasks取餘。其實現如下:

public class HashPartitioner<K, V> extends Partitioner<K, V> {

  public int getPartition(K key, V value,
                          int numReduceTasks) {
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
  }

}

6.2 自定義Partitioner

這裡我們繼承Partitioner自定義分類規則,這裡按照單詞進行分類:

public class CustomPartitioner extends Partitioner<Text, IntWritable> {

    public int getPartition(Text text, IntWritable intWritable, int numPartitions) {
        return WordCountDataUtils.WORD_LIST.indexOf(text.toString());
    }
}

在構建job時候指定使用我們自己的分類規則,並設定reduce的個數:

// 設定自定義分割槽規則
job.setPartitionerClass(CustomPartitioner.class);
// 設定reduce個數
job.setNumReduceTasks(WordCountDataUtils.WORD_LIST.size());

6.3 執行結果

執行結果如下,分別生成6個檔案,每個檔案中為對應單詞的統計結果:

Hadoop 三劍客之 —— 分散式計算框架 MapReduce

參考資料

  1. 分散式計算框架MapReduce
  2. Apache Hadoop 2.9.2 > MapReduce Tutorial
  3. MapReduce - Combiners

更多大資料系列文章可以參見個人 GitHub 開源專案: 大資料入門指南

相關文章