開發適用於 HDInsight 上 Apache Hadoop 的 Java MapReduce 程式

了解如何使用 Apache Maven 來建立以 Java 為基礎的 MapReduce 應用程式,然後在 Azure HDInsight 上使用 Apache Hadoop 來加以執行。

必要條件

設定開發環境

用於本文的環境是執行 Windows 10 的電腦。 命令已在命令提示字元中執行,而且各種檔案已使用記事本進行編輯。 針對您的環境據以修改。

從命令提示字元中,輸入下列命令以建立工作環境:

IF NOT EXIST C:\HDI MKDIR C:\HDI
cd C:\HDI

建立 Maven 專案

  1. 輸入下列命令建立名為 wordcountjava 的 Maven 專案:

    mvn archetype:generate -DgroupId=org.apache.hadoop.examples -DartifactId=wordcountjava -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
    

    此命令會使用 artifactID 參數所指定的名稱 (此範例中為 wordcountjava) 來建立目錄。此目錄包含下列項目:

    • pom.xml - 專案物件模型 (POM),包含用來建置專案的資訊和組態詳細資料。
    • src\main\java\org\apache\hadoop\examples:包含應用程式程式碼。
    • src\test\java\org\apache\hadoop\examples:包含應用程式的測試。
  2. 移除所產生的範例程式碼。 輸入下列命令,以刪除產生的測試和應用程式檔案 AppTest.javaApp.java

    cd wordcountjava
    DEL src\main\java\org\apache\hadoop\examples\App.java
    DEL src\test\java\org\apache\hadoop\examples\AppTest.java
    

更新專案物件模型

如需 pom.xml 檔案的完整參考,請參閱 https://maven.apache.org/pom.html。 輸入下列命令以開啟 pom.xml

notepad pom.xml

新增相依性

pom.xml 中,在 <dependencies> 區段中新增下列文字:

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-examples</artifactId>
    <version>2.7.3</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-common</artifactId>
    <version>2.7.3</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>2.7.3</version>
    <scope>provided</scope>
</dependency>

這會定義含有特定版本 (列於<version> 內) 的必要程式庫 (列於 <artifactId> 內)。 在編譯期間,會從預設的 Maven 儲存機制下載這些相依性。 您可以使用 Maven 儲存機制搜尋 (英文) 檢視詳細資訊。

<scope>provided</scope> 會告訴 Maven 這些相依性不應該和應用程式一起封裝,因為 HDInsight 叢集會在執行階段提供這些相依性。

重要

使用的版本應該符合您叢集上的 Hadoop 版本。 如需有關版本的詳細資訊,請參閱 HDInsight 元件版本設定文件。

組建組態

Maven 外掛程式可讓您自訂專案的建置階段。 此區段會用來新增外掛程式、資源,和其他組建組態選項。

將下列程式碼加入 pom.xml 檔案,然後儲存並關閉該檔案。 此文字必須位在檔案中的 <project>...</project> 標籤內,例如在 </dependencies></project> 之間。

<build>
    <plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>2.3</version>
        <configuration>
        <transformers>
            <transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer">
            </transformer>
        </transformers>
        </configuration>
        <executions>
        <execution>
            <phase>package</phase>
                <goals>
                <goal>shade</goal>
                </goals>
        </execution>
        </executions>
        </plugin>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.6.1</version>
        <configuration>
        <source>1.8</source>
        <target>1.8</target>
        </configuration>
    </plugin>
    </plugins>
</build>

此區段會設定 Apache Maven Compiler 外掛程式和 Apache Maven Shade 外掛程式。 Compiler 外掛程式用來編譯拓撲。 Shade 外掛程式用來防止以 Maven 所建置的 JAR 封裝發生授權重複。 此外掛程式是用於防止 HDInsight 叢集在執行階段發生「重複的授權檔案」錯誤。 使用 maven-shade-plugin 搭配 ApacheLicenseResourceTransformer 實作可防止此錯誤。

maven-shade-plugin 也會產生 uber jar,其中含有應用程式需要的所有相依性。

儲存pom.xml檔案。

建立 MapReduce 應用程式

  1. 輸入下列命令以建立並開啟新的檔案 WordCount.java。 在命令提示字元中選取 [是],以建立新的檔案。

    notepad src\main\java\org\apache\hadoop\examples\WordCount.java
    
  2. 接著複製下方的 Java 程式碼並將其貼入新的檔案中。 然後關閉檔案。

    package org.apache.hadoop.examples;
    
    import java.io.IOException;
    import java.util.StringTokenizer;
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.IntWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.Job;
    import org.apache.hadoop.mapreduce.Mapper;
    import org.apache.hadoop.mapreduce.Reducer;
    import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
    import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
    import org.apache.hadoop.util.GenericOptionsParser;
    
    public class WordCount {
    
        public static class TokenizerMapper
            extends Mapper<Object, Text, Text, IntWritable>{
    
        private final static IntWritable one = new IntWritable(1);
        private Text word = new Text();
    
        public void map(Object key, Text value, Context context
                        ) throws IOException, InterruptedException {
            StringTokenizer itr = new StringTokenizer(value.toString());
            while (itr.hasMoreTokens()) {
            word.set(itr.nextToken());
            context.write(word, one);
            }
        }
    }
    
    public static class IntSumReducer
            extends Reducer<Text,IntWritable,Text,IntWritable> {
        private IntWritable result = new IntWritable();
    
        public void reduce(Text key, Iterable<IntWritable> values,
                            Context context
                            ) throws IOException, InterruptedException {
            int sum = 0;
            for (IntWritable val : values) {
            sum += val.get();
            }
            result.set(sum);
            context.write(key, result);
        }
    }
    
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
        if (otherArgs.length != 2) {
            System.err.println("Usage: wordcount <in> <out>");
            System.exit(2);
        }
        Job job = new Job(conf, "word count");
        job.setJarByClass(WordCount.class);
        job.setMapperClass(TokenizerMapper.class);
        job.setCombinerClass(IntSumReducer.class);
        job.setReducerClass(IntSumReducer.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
        System.exit(job.waitForCompletion(true) ? 0 : 1);
        }
    }
    

    請注意,封裝名稱是 org.apache.hadoop.examples,而類別名稱是 WordCount。 提交 MapReduce 作業時會用到這些名稱。

建置和封裝應用程式

wordcountjava 目錄,使用下列命令來建置含有應用程式的 JAR 檔案:

mvn clean package

此命令會清除任何先前的組建構件、下載任何尚未安裝的相依性,然後建置並封裝應用程式。

一旦命令完成之後,wordcountjava/target 目錄就會包含名為 wordcountjava-1.0-SNAPSHOT.jar 的檔案。

注意

wordcountjava-1.0-SNAPSHOT.jar 檔案是一個 uberjar,其中不只含有 WordCount 作業,還有該作業在執行階段所需的相依性。

上傳 JAR 並執行作業 (SSH)

下列步驟使用 scp,將 JAR 複製到 HDInsight 叢集上 Apache HBase 的主要前端節點。 接著,會使用 ssh 命令連接到該叢集並直接在前端節點上執行範例。

  1. 將 jar 上傳至叢集。 將 CLUSTERNAME 取代為 HDInsight 叢集名稱,然後輸入下列命令:

    scp target/wordcountjava-1.0-SNAPSHOT.jar sshuser@CLUSTERNAME-ssh.azurehdinsight.net:
    
  2. 連線至叢集。 將 CLUSTERNAME 取代為 HDInsight 叢集名稱,然後輸入下列命令:

    ssh sshuser@CLUSTERNAME-ssh.azurehdinsight.net
    
  3. 在 SSH 工作階段中,使用以下命令執行 MapReduce 應用程式:

    yarn jar wordcountjava-1.0-SNAPSHOT.jar org.apache.hadoop.examples.WordCount /example/data/gutenberg/davinci.txt /example/data/wordcountout
    

    此命令會啟動 WordCount MapReduce 應用程式。 輸入檔案為 /example/data/gutenberg/davinci.txt,輸出目錄則為 /example/data/wordcountout。 輸入檔和輸出都會儲存至叢集的預設儲存體中。

  4. 作業完成之後,請使用以下命令來檢視結果:

    hdfs dfs -cat /example/data/wordcountout/*
    

    您應該會收到一份單字和計數的清單,其中含有類似下列文字的值:

    zeal    1
    zelus   1
    zenith  2
    

下一步

在本文件中,您已學到如何開發 Java MapReduce 工作。 請參閱下列文件,了解其他的 HDInsight 使用方式。