hadoop任务提交过程


  • WordCountMapper:

    private final static IntWritable one = new IntWritable(1);
    @Override
    public void map(LongWritable key, Text value, 
            Context context) throws IOException, InterruptedException {
        StringTokenizer tokenizer = new StringTokenizer(value.toString());
        while (tokenizer.hasMoreTokens()) {
            String str = tokenizer.nextToken();
            context.write(new Text(StringUtils.trim(str.replaceAll("\\W", ""))), one);
        }
    }

    WordCountReduce:

    @Override
    public void reduce(Text key, Iterable
    
     
    
     
      
     
      
      values,
            Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable val : values) {
                sum += val.get();
        }
        context.write(key, new IntWritable(sum));
        }
    
    
     
    
     

    WordCount:

    final Configuration conf = new Configuration();
    Job job = new Job(conf);
    job.setJobName("wordcount");
    job.setJarByClass(WordCount.class);
    
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    
    job.setMapperClass(WordCountMapper.class);
    job.setReducerClass(WordCountReduce.class);
    
    job.setInputFormatClass(TextInputFormat.class);
    job.setOutputFormatClass(TextOutputFormat.class);
    
    FileInputFormat.setInputPaths(job, new Path(args[0]));
    FileOutputFormat.setOutputPath(job, new Path(args[1]));
    
    boolean success = job.waitForCompletion(true);

    如你所见, 这是个Hadoop基础的入门例子, 如果你了解Hadoop, 你已经对这些代码熟记于心了.这篇文章我想说明Hadoop提交Job到底提交了那些东西,提交了哪些类,不需要提交那些东西.

    Hadoop任务提交

    传统的Hadoop任务提交

    把上面的代码 WordCountMapper, WordCountReduce, WordCount打包成jar,放到hadoop目录下, 使用hadoop jar wordcount.jar WordCount运行任务. 这样方式我称为传统的方式,也是《hadoop权威指南》上一贯的方法.

    Eclipse的hadoop插件的Hadoop任务提交

    如果你开发过Hadoop的Job, 那么对这个应该很熟悉.大多数开发测试都是用这个提交任务的,如果每次都是打包成jar, 再用hadoop jar 这还不把人搞疯.

    如果你还细心,你会发现,你选好Hadoop的jobtracker,提交任务的前一刻,Eclipse会弹出一个浮动窗口,上面跳动着显示很多jar名.为什么会这样?它做了什么?

    hadoop任务提交过程_第1张图片

    在Eclipse中当做Java Application运行为什么不可以?

    Hadoop的job项目都有main方法,这个是符合JavaApplication运行条件的,那么我们是不是可以使用Eclipse中直接运行呢?当我们尝试运行的时候,程序是可以运行的,但总当运行一会儿(几秒钟)后抛出WordCountMapper ClassNotFount的错误.

    那么为什么程序不是直接抛出错误而是过了一会儿才抛出?为什么用Eclipse Hadoop插件运行不会发生这个错误.

    背景

    写这篇文章前,我们已经正在开发一个Hadoop的任务调度的系统. 也就是一个项目中提前写好很多个Hadoop Job绑定到里边,如果想要运行哪个Job,我们就从前台配置好参数,并把这个Job提交到Hadoop集群. 并从前台不断的得到Job的运行任务信息,取得Job的执行进度. 不关心进度的话直接去喝茶就可以,完了来看结果.

    如果每次都是hadoop jar wordcount.jar WordCount未免太弱智. 如果每次从Eclispe中运行,那专业性太强,也不可取.是不是有更好的方法提交任务?

    答案肯定是有的.

    用JVisualVM监视Eclipse hadoop插件的Hadoop任务提交

    打开JVisualVM准备着,运行一个Job. 运行后立即就可以看到一个Java进程. 用JVisualVM打开这个进程查看,如图:

    在此输入图片描述

    在此输入图片描述

    我打开我电脑上的目录F:\Eclipse\workspace.metadata.plugins\org.apache.hadoop.eclipse\hadoop-conf-1007657720166395816并且查看了它的上级目录,顿时一些皆明朗了.Eclipse Hadoop插件竟然把我的项目下所有的类,资源文件打包成jar然后运行的.

    hadoop任务提交过程_第2张图片

    hadoop任务提交过程_第3张图片

    使用Hadoop Api提交Job,完美解决方案

    其实我也是从这篇文章(http://luliangy.iteye.com/blog/1401453)中找到灵感的.为什么把项目打包,以Java Application的方式就正常运行了.

    ((JobConf) job.getConfiguration()).setJar("wordcount.jar");  
    job.setJarByClass(WordCount.class);

    冲着这股劲我看了很多Hadoop API,终于找到为什么.

    通过job.setJarByClass(WordCount.class); 这条路查看源码, 你会找到如下的两个方法.

    private static String findContainingJar(Class my_class) {
        ClassLoader loader = my_class.getClassLoader();
        String class_file = my_class.getName().replaceAll("\\.", "/") + ".class";
        try {
          for(Enumeration itr = loader.getResources(class_file);
              itr.hasMoreElements();) {
            URL url = (URL) itr.nextElement();
            if ("jar".equals(url.getProtocol())) {
              String toReturn = url.getPath();
              if (toReturn.startsWith("file:")) {
                toReturn = toReturn.substring("file:".length());
              }
              toReturn = URLDecoder.decode(toReturn, "UTF-8");
              return toReturn.replaceAll("!.*$", "");
            }
          }
        } catch (IOException e) {
          throw new RuntimeException(e);
        }
        return null;
      }
    
    public void setJarByClass(Class cls) {
        String jar = findContainingJar(cls);
        if (jar != null) {
          setJar(jar);
        }   
    }

    我说说findContainingJar有什么作用? 当使用job.setJarByClass(WordCount.class);设置类的时候, Hadoop Client能从你的classpath中取得WordCount.class所在的jar包的jar File绝对路径.如果找不到jar, setJar(jar);方法没有执行,jar肯定是个空值.

    我们在Eclipse中直接以Java Application运行的时候,classpath是一个本地文件夹, findContainingJar肯定找不到项目的jar.也就是Mapper和Reduce所在的jar. 这样在提交任务时候Configuration中mapper.jar属性是一个空值.这也就解释了为什么在Eclipse中当做Java Application运行时总是过一段时间后才发生ClassNotFound的错误原因.

    其实到这里Hadoop提交了什么也好解释了.

    Hadoop向集群中提交了一个xml和一个携带Mapper/Reduce的jar. xml就是Configuration对象序列化的结果.

    说到这里也许你已经发现,这是一个开发上的架构问题.既然Hadoop Job需要Map/Reduce的jar.我们应该把所有的Map/Reduce单独在一个项目中开发.然后打包放入调度系统项目的ClassPath就好了.然后在调度系统中构造Job,并把job.setJarByClass(class);中的class设置为该Job的map clas或者reduce class就行了.

    哪些是在Client执行的?哪些是在Hadoop集群中执行?

    一个Hadoop 任务一般都有3个类(Map/Reduce/Job).WordCountMapper, WordCountReduce, WordCount你认为这三个类都会提交到集群中执行吗?

    不是! 只有Mapper和Reduce这2个类会提交到Hadoop集群, MapReduce执行也是这2个类. WordCount只是充当一个配置Job的客户端,并且提交任务,之后又定时轮询Job的运行状态输出简单的日志,直到任务完成,WordCount的这个进程会自动退出.

    Hadoop分布式缓存

    讲到这里你也许能顺利的实现一个和我相同思路的系统了.

    但是我还是想说一个常见错误. 不是Mapper Class NotFound,而是Mapper中使用的Class NotFound. 而你又不想往hadoop集群中添加jar包,也不想重启Hadoop集群. 你可以使用Hadoop提供的一个类:DistributedCache

    1. DistributedCache.addArchiveToClassPath() 添加hdfs上的jar到MapReduce的classpath
    2. DistributedCache.addCacheFile(new URI(“/myapp/lookup.dat#lookup.dat”), job);
    3. DistributedCache.addCacheArchive(new URI(“/myapp/map.zip”, job);
    4. DistributedCache.addFileToClassPath(new Path(“/myapp/mylib.jar”), job);
    5. DistributedCache.addCacheArchive(new URI(“/myapp/mytar.tar”, job);
    6. DistributedCache.addCacheArchive(new URI(“/myapp/mytgz.tgz”, job);
    7. DistributedCache.addCacheArchive(new URI(“/myapp/mytargz.tar.gz”, job);

    你可能感兴趣的:(hadoop)