给力星

Web Developer

使用命令行编译打包运行自己的MapReduce程序 Hadoop2.6.0

网上的 MapReduce WordCount 教程对于如何编译 WordCount.java 几乎是一笔带过… 而有写到的,大多又是 0.20 等旧版本版本的做法,即 javac -classpath /usr/local/hadoop/hadoop-1.0.1/hadoop-core-1.0.1.jar WordCount.java,但较新的 2.X 版本中,已经没有 hadoop-core*.jar 这个文件,因此编辑和打包自己的 MapReduce 程序与旧版本有所不同。

本文以 Hadoop 2.6.0 环境下的 WordCount 实例来介绍 2.x 版本中如何编辑自己的 MapReduce 程序。

Hadoop 2.x 版本中的依赖 jar

Hadoop 2.x 版本中 jar 不再集中在一个 hadoop-core*.jar 中,而是分成多个 jar,如使用 Hadoop 2.6.0 运行 WordCount 实例至少需要如下三个 jar:

  • $HADOOP_HOME/share/hadoop/common/hadoop-common-2.6.0.jar
  • $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-client-core-2.6.0.jar
  • $HADOOP_HOME/share/hadoop/common/lib/commons-cli-1.2.jar

实际上,通过命令 hadoop classpath 我们可以得到运行 Hadoop 程序所需的全部 classpath 信息。

编译、打包 Hadoop MapReduce 程序

我们将 Hadoop 的 classhpath 信息添加到 CLASSPATH 变量中,在 ~/.bashrc 中增加如下几行:

export HADOOP_HOME=/usr/local/hadoop
export CLASSPATH=$($HADOOP_HOME/bin/hadoop classpath):$CLASSPATH

别忘了执行 source ~/.bashrc 使变量生效,接着就可以通过 javac 命令编译 WordCount.java 了(使用的是 Hadoop 源码中的 WordCount.java,源码在文本最后面):

javac WordCount.java

编译时会有警告,可以忽略。编译后可以看到生成了几个 .class 文件。

使用Javac编译自己的MapReduce程序使用Javac编译自己的MapReduce程序

接着把 .class 文件打包成 jar,才能在 Hadoop 中运行:

jar -cvf WordCount.jar ./WordCount*.class

打包完成后,运行试试,创建几个输入文件:

mkdir input
echo "echo of the rainbow" > ./input/file0
echo "the waiting game" > ./input/file1

创建WordCount的输入创建WordCount的输入

开始运行:

/usr/local/hadoop/bin/hadoop jar WordCount.jar WordCount input output

不过这边可能会遇到如下的提示 Exception in thread "main" java.lang.NoClassDefFoundError: WordCount

提示找不到 WordCount 类提示找不到 WordCount 类

因为程序中声明了 package ,所以在命令中也要 org.apache.hadoop.examples 写完整:

/usr/local/hadoop/bin/hadoop jar WordCount.jar org.apache.hadoop.examples.WordCount input output

正确运行后的结果如下:

WordCount 运行结果WordCount 运行结果

进阶:使用 Eclipse 编译运行 MapReduce 程序

使用命令行编译运行MapReduce程序毕竟有些麻烦,修改一次就得手动编译、打包一次,使用Eclipse编译运行MapReduce程序会更加方便。

WordCount.java 源码

文件位于 hadoop-2.6.0-src\hadoop-mapreduce-project\hadoop-mapreduce-examples\src\main\java\org\apache\hadoop\examples 中:

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
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);
  }
}

参考资料

  1. 星哥 我做了四機的架設 運行wordcount時速度上確實有所提升
    但執行自己的程式時出現了錯誤
    hadoop@Master:~$ /usr/local/hadoop/bin/hadoop jar OneStepMatrixMultiplication.jar org.apache.hadoop.examples.OneStepMatrixMultiplication input output
    Exception in thread "main" java.lang.ClassNotFoundException: org.apache.hadoop.examples.OneStepMatrixMultiplication
    at java.net.URLClassLoader$1.run(URLClassLoader.java:359)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:348)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:347)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:278)
    at org.apache.hadoop.util.RunJar.run(RunJar.java:214)
    at org.apache.hadoop.util.RunJar.main(RunJar.java:136)
    這是哪邊出了問題呢 麻煩您了

  2. 您好 我想詢問該如何把三个jar包加入到classpath中呢?

  3. 我根据您http://www.powerxing.com/install-hadoop/这个博客搭建好Hadoop,也可以运行Wordcount.java程序,但是jps后发现没有jobtracker和tasktracker,这是为什么?会不会对我运行其他程序有影响?谢谢

  4. 如果说节点越多,运行效率越高的话,那在伪分布式情况下如何设置节点数量呢,谢谢。

    • 伪分布式的意思是说,datanode和namenode运行于同一节点上,实际上也就是只有一个节点,如果你要达到多节点的效果,请参考分布式集群设置。

        • 伪分布式是分布式的一个特例,是指集群中只有一个节点,同时兼任 namenode 和 datanode 的角色。并没有“伪分布式的分布式”这种说法。

  5. 想请教下楼主。
    我对java不熟,想知道package的org.apache.hadoop.examples,这里org在哪个目录下,应该怎么找?
    还有我想知道那些import的包在什么路径下,所以看了下CLASSPATH,发现有好多,可以确定是哪一个吗?一般是放jar里头还是文件夹里头有公识吗?

    • 1. 你把 WordCount.jar 解压出来,就明白这个目录关系了。
      2. 如何确定我还真不清楚,但从名称上可以看出个大概,比如hadoop-mapreduce-client-core-2.6.0.jar这个,应该是对应了程序中 org.apache.hadoop.mapreduce.* 的。

  6. 真难得看到那么详细的教程!!多谢楼主
    想问下output应该是在hdfs里把,那是否cat也应该用hadoop的cat?这点不理解

  7. 之前用过伪分布式进行运行,也用过Eclipse运行,现在再用单机模式运行文中例子就出现:
    Exception in thread "main" java.net.ConnectException: Call From localhost:9000 failed on connection exception: java.net.ConnectionException: connection refused, 望解答。谢谢。

    • 因为配置文件修改过了,此时并不再是"单机模式"了~ 运行模式其实取决于当前的配置文件的。你把伪分布式有配置过的那些配置项都删掉,再用单机模式跑,就不会出现这个问题了

  8. 还有运行jar文件命令中为什么要加org.apache.hadoop.examples.WordCount啊?是因为这种粗暴的打jar包的方式没有指定主class吗?我用eclipse打包的就不需要加这个

    • 是的,没有指定入口类,用 jar 命令打包的时候可以加上一个 manifest 文件,在其中指定 Main-Class,运行 jar 文件时就不用加那一长串东西了。

  9. 请问CLASSPATH=$($HADOOP_HOME/bin/hadoop classpath)这是执行一个脚本文件把对应的classpath添加进去吗?好高级的写法。windows下这样写吗CLASSPATH=%(%HADOOP_HOME%/bin/hadoop classpath)%,好像不行啊

    • 是的,其实就是执行了 $HADOOP_HOME/bin/hadoop classpath 这个命令,把返回的结果字符串作为CLASSPATH的值。

      windows下设置环境变量的方式为: set CLASSPATH=…. 你可以再试一下。

  10. ^C[hadoop@master /]$ hadoop jar ~/maxtem.jar hdfs://master:9000/user hdfs://master:9000/outs
    16/07/21 10:31:48 INFO client.RMProxy: Connecting to ResourceManager at master/192.168.116.128:8032
    16/07/21 10:31:49 INFO input.FileInputFormat: Total input paths to process : 3
    16/07/21 10:31:49 INFO mapreduce.JobSubmitter: number of splits:3
    16/07/21 10:31:49 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1469118061012_0003
    16/07/21 10:31:51 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:31:53 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:31:56 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:31:58 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:00 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:02 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:04 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:06 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:08 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:10 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:12 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:14 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:16 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:18 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:20 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:22 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:24 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:26 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:28 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:31 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:33 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW
    16/07/21 10:32:35 INFO impl.YarnClientImpl: Application submission is not finished, submitted application application_1469118061012_0003 is still in NEW

    请问下楼主这个是什么情况?

  11. 楼主可以把所有依赖包发一下,实在对依赖的jar包头痛

      • 请问星哥,执行这个命令出来的结果,是在hadoop-env.sh里面设置的hadoop_classpath里面的结果吗?

  12. Thanks!Because of your work,my first hadoop example (WordCount) had done perfectly!

  13. 为什么?
    hadoop jar WordCount.jar org.apache.hadoop.examples.WordCount input output
    16/04/15 01:07:08 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable
    16/04/15 01:07:09 INFO client.RMProxy: Connecting to ResourceManager at /0.0.0.0:8032
    16/04/15 01:07:10 INFO mapreduce.JobSubmitter: Cleaning up the staging area /tmp/hadoop-yarn/staging/hadoop/.staging/job_1456990604986_0004
    Exception in thread “main” org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: hdfs://localhost:9000/user/hadoop/input
    at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.singleThreadedListStatus(FileInputFormat.java:321)
    at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.listStatus(FileInputFormat.java:264)
    at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.getSplits(FileInputFormat.java:385)
    at org.apache.hadoop.mapreduce.JobSubmitter.writeNewSplits(JobSubmitter.java:597)
    at org.apache.hadoop.mapreduce.JobSubmitter.writeSplits(JobSubmitter.java:614)
    at org.apache.hadoop.mapreduce.JobSubmitter.submitJobInternal(JobSubmitter.java:492)
    at org.apache.hadoop.mapreduce.Job$10.run(Job.java:1296)
    at org.apache.hadoop.mapreduce.Job$10.run(Job.java:1293)
    at java.security.AccessController.doPrivileged(Native Method)
    at javax.security.auth.Subject.doAs(Subject.java:415)
    at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1628)
    at org.apache.hadoop.mapreduce.Job.submit(Job.java:1293)
    at org.apache.hadoop.mapreduce.Job.waitForCompletion(Job.java:1314)
    at org.apache.hadoop.examples.WordCount.main(WordCount.java:87)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.apache.hadoop.util.RunJar.run(RunJar.java:221)
    at org.apache.hadoop.util.RunJar.main(RunJar.java:136)

    • Exception in thread “main” org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: hdfs://localhost:9000/user/hadoop/input

      读取的是 HDFS 中的 input 文件夹,首先得先上传文件啊

  14. 请问博主,从哪里能得到具体、全面的hadoop2的资料?

    • 要具体、全面的,那自然是官方文档了。
      要么就是看一些相关的书籍、网站、博客。

      • 恩恩 谢谢~
        还有一个地方,就是官网下载的镜像都是64位的hadoop,对于32的ubuntu必须用32位的hadoop,所以出现这个警告WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform… using builtin-java classes where applicable。用hadoop的源码自己重新编译成32位的,问题才解决。[呵呵]

      • 你的程序没有运行成功,从你上面贴的运行信息来看,Hadoop可能都没有正确启动。
        Exception in thread “main” java.net.ConnectException: Call From ubuntu/127.0.1.1 to localhost:9000 failed on connection exception:

        先检查Hadoop有没有正确启动,先运行example试试,可以的话,再运行自己的程序。

  15. 星哥,
    16/04/12 07:00:03 INFO Configuration.deprecation: session.id is deprecated. Instead, use dfs.metrics.session-id
    16/04/12 07:00:03 INFO jvm.JvmMetrics: Initializing JVM Metrics with processName=JobTracker, sessionId=
    Exception in thread “main” java.net.ConnectException: Call From ubuntu/127.0.1.1 to localhost:9000 failed on connection exception: java.net.ConnectException: Connection refused; For more details see: http://wiki.apache.org/hadoop/ConnectionRefused
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    我的结果总是这样,我关了dfs也不行。。。

  16. jar -cvf WordCount.jar ./WordCount*.class

    added manifest
    adding: WordCount.class(in = 1911) (out= 996)(deflated 47%)
    adding: WordCount$IntSumReducer.class(in = 1793) (out= 750)(deflated 58%)
    adding: WordCount$TokenizerMapper.class(in = 1790) (out= 765)(deflated 57%)

  17. 星哥,我编译wordcount的时候说找不到我的java文件,怎么办呢?hadoop@ubuntu:~$ javac WordCount.java
    javac: file not found: WordCount.java
    Usage: javac
    use -help for a list of possible options

    • file not found那就是WordCount.java不是放在当前目录下了。
      会不会是你WordCount.java是放在 ~/wordcount/ 里,但是上述命令是在 ~/ 里执行的?

      • 星哥 我顺着你第一段的目录/usr/local/hadoop/hadoop-1.0.1/hadoop-core-1.0.1.jar找了一下,发现我的local里面的hadoop并不包含hadoop-1.0.1哎,只有bin,etc,include,input,lib,libexec,logs,output,sbin,share,tmp,还有三个text文件

        • 那说的是“大多又是 0.20 等旧版本版本的做法,即 javac -classpath /usr/local/hadoop/hadoop-1.0.1/hadoop-core-1.0.1.jar“
          那是旧的0.20版本

          • WordCount.java 就是文章最后面给出的那个代码。
            你可以在 ~ 下建立一个 WordCount.java 文件

            cd ~
            vim WordCount.java

            然后把代码复制进去
            再在 ~ 下执行 javac WordCount.java 就可以了啊

  18. 好心塞 再也不敢乱试了。。。。能不能麻烦复制个正确版本给我,,,我该回去,,,唉,,哭死了

  19. 我想问下,设置package为org.apache.hadoop.examples有什么特殊含义吗?为什么我用这个package可以把wordcount运行成功,换另一个package就出错,出现ClassNotFoundException?

    • 代码中第一行声明了 package org.apache.hadoop.examples; 如果你要改成别的,代码也要相应改动。

      • 我这次只是更改了一下类的名字。其余部分没有改动,执行报错,各种找不到类,这个错误简直烦死了。。而且,我按你说的,把那三个jar包加到classpath,编译找不到类。所以我都是打包eclipse编译好的class文件。只有执行你给的wordcount成功了,把类名改成wordcount1都报错ClassNotFoundException。。

        • 把类名改成 WordCount1 需要把代码中第 34 行的 public class WordCount { 改成 public class WordCount1 {,同时需要把文件名也改成 WordCount1.java … 请认真学习 Java 基础知识 ……

          • 我按照教程,把三个jar包加入到classpath中,只是把2.4.1改为2.6.0,并且确定是存在这三个jar包的。但是接着执行javac WordCount.java 出现错误,有多个程序包找不到,例如org.apache.hadoop.conf以及org.apahe.hadoop.fs等等。。

          • classpath中用到了$HADOOP_HOME,这个变量需要设置,否则会出错。我试了,在Hadoop 2.6.0下可以正确编译。教程已修改。

  20. 我編譯的時候,我有類似這樣的錯誤error: package org.apache.hadoop.fs does not existimport org.apache.hadoop.fs.Path我是使用hadoop 2.7.0,我的classpath如下hduser@tony-virtual-machine:~$ echo $CLASSPATH/usr/local/hadoop/share/hadoop/common/hadoop-common-2.7.0.jar:/usr/local/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-client-core-2.7.0.jar:/usr/local/hadoop/share/hadoop/common/lib/commons-cli-1.2.jar:

  21. 为什么运行jar文件的时候老是提醒这个Exception in thread “main” org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: hdfs://localhost:9000/user/hadoop/input

    • 我这教程用的是单机模式。但从你的提示来看,你用的是伪分布式模式,你需要将输入文件复制到hdfs的input文件夹中。