MapReduce程式调用第三方包和本地库

问题提出:
在MP程式中如何在不同的TaskTracker节点上调用第三方jar包和读取一些只读的文件。

解决方法介绍:
我们知道,在Hadoop中有一个叫做DistributedCache的东东,它是用来分发应用特定的只读文件和一个jar包的,以供Map-Reduce框架在启动任务和运行的时候使用这些缓冲的文件或者是把第三方jar包添加到其classpath路径中去,要注意的是DistributedCache的使用是有一个前提的,就它会认为这些通过urls来表示的文件已经在hdfs文件系统里面,所以这里在使用的时候第一步就是要把这些文件上传到HDFS中。

然后Hadoop框架会把这些应用所需要的文件复制到每个准备启动的节点上去,它会把这些复制到mapred.temp.dir配置的目录中去,以供相应的Task节点使用。

这里要注意的DistriubtedCache分发的文件分成公有与私有文件,公有文件可以给HDFS中的所有用户使用,而私有文件只能被特定的用户所使用,用户可以配置上传文件的访问权限来达到这种效果。

DistributeCache的使用一般分成三步:
1. 配置应用程序的cache,把需要使用的文件上传到DFS中去

$ bin/hadoop fs -copyFromLocal lookup.dat /myapp/lookup.dat
$ bin/hadoop fs -copyFromLocal map.zip /myapp/map.zip
$ bin/hadoop fs -copyFromLocal mylib.jar /myapp/mylib.jar
$ bin/hadoop fs -copyFromLocal mytar.tar /myapp/mytar.tar
$ bin/hadoop fs -copyFromLocal mytgz.tgz /myapp/mytgz.tgz
$ bin/hadoop fs -copyFromLocal mytargz.tar.gz /myapp/mytargz.tar.gz

2. 配置JobConf

JobConf job = new JobConf();
DistributedCache.addCacheFile(new URI(“/myapp/lookup.dat#lookup.dat”),job); // 这里的lookup.dat加了一个符号连接
DistributedCache.addCacheArchive(new URI(“/myapp/map.zip”, job);
DistributedCache.addFileToClassPath(new Path(“/myapp/mylib.jar”), job); // 这里是把相应的jar包加到Task的启动路径上去
DistributedCache.addCacheArchive(new URI(“/myapp/mytar.tar”, job);
DistributedCache.addCacheArchive(new URI(“/myapp/mytgz.tgz”, job);
DistributedCache.addCacheArchive(new URI(“/myapp/mytargz.tar.gz”, job);

3. 在Mapper或者Reducer任务中使用这些文件

public  static  class MapClass  extends MapReduceBase    
     implements Mapper <K, V, K, V >  {  
       private Path [ ] localArchives ;  
       private Path [ ] localFiles ;  
       public  void configure (JobConf job )  {  
         // Get the cached archives/files  
        localArchives  = DistributedCache. getLocalCacheArchives (job ) ;   // 得到本地打包的文件,一般是数据文件,如字典文件  
        localFiles  = DistributedCache. getLocalCacheFiles (job ) ;         // 得到本地缓冲的文件,一般是配置文件等  
       }  
       public  void map (K key, V value,   
                      OutputCollector <K, V > output, Reporter reporter )   
       throws  IOException  {  
         // Use data from the cached archives/files here  
         // ...  
         // ...  
        output. collect (k, v ) ;  
       }  
     }

使用新的MP接口要注意的地方:
1. 我们知道,新的MP接口使用了Job这个类来对MP任务进行配置,这里使用的时候要注意一点
Configuration conf = new Configuration();
// 对conf加入配置信息 – 正确方法
Job job = new Job(conf,”word count”);
// 对conf加入配置信息 – 这是有问题的,这些配置不会生效,因为这里生成Job的时候它会对conf进行复制,这个看一下Job的源代码就知道。
// 这里可以用job.getConfiguration()来得到其内部的conf对象,这样就不会有问题。

2. 如果你在启动MP任务之前调用了第三方jar包的类,那这就会有问题,会在启动任务的时候找不到这个类。这个问题我还没有找到好的解决办法,一个办法就是把这些类想办法移到MP任务中,如果有朋友知道更加好的办法,请告诉我一下,多谢了。我感觉Nutch中也会有同样的问题,什么时候研究一下Nutch的代码,说不定会有很多关于Hadoop方面的收获。

参考:

1. http://hadoop.apache.org/common/docs/current/api/org/apache/hadoop/filecache/DistributedCache.html
2. http://hadoop.apache.org/common/docs/current/mapred_tutorial.html#IsolationRunner

Hadoop 之 Secondary Sort介绍

我们知道,在reduce之前,MP框架会对收到的对按K进行排序,而对于一个特定的K来说,它的List是没有被排过序的,就是说这些V是无序的,因为它们来自不同的Map端,而且很多应用也不依赖于K所对应的list的顺序,但是有一些应用就要就要依赖于相同K的V的顺序,而且还要把他们聚合在一起,下面会提出这样一个问题,是参考Hadoop The Defiinitive Guide的第八章。

1. 问题的提出

对于如下数据,我们要计算出每一年的最高温度值:

(1900,34)
(1900,32)
….
(1950, 0)
(1950, 22)
(1950, −11)
(1949, 111)
(1949, 78)
计算结果可能如下:

1901 317
1902 244
1903 289
1904 256

1949 111

我们一般的方法是把key设置成年份,把value设置成温度,在reduce的时候去遍历所有相同key的value值,找出最大的那个值,在Reduce返回的时候,只collect这个最大的值,这是一种办法,但是这种办法在效率上相对比较差,也不够灵活,下面我们来看看怎么使用Secondary Sort来解决这个问题

2. Secondary Sort

Secondary Sort 实际上就是一种对Value进行二次排序,然后按key的特定部分进行聚合的方法,这里用到了一个组合Key的概念,就是把K与要排序的Value组合在一起,生成一个新的Key值,上面的例子中,新的组合key为<1900,32>,也就是<年份,温度>的组合,(1900, 35°C),(1900, 34°C),这样组合以后,生成一个新的key,但是这样组合以后,它们会被切分到不同的Reduce上,所以我们这里要写一个根据新组合的key的第一个参数(年份)来进行相应的partitioner,在JobConf中可以使用setPartitionerClass来进行设置,这样可以解决相同年份的key会被聚合在同一个Reduce上,但是还没有解析在同一个Reduce上,把部分key相同的记录聚合(group)在一起,所以这里我们要设置一个group的比较器,这样就可以把相同年份的记录聚合在一起,但对于相同key(这里是指key中第一个参数相同)的排序问题,我们要使用一个KeyComparator比较器来做,就是在group中对key进行二次排序,在上面例子中就是按key中第二个参数温度来降序排序,这里要注意的是这里的输入是key,而不是value,这就是我们为什么把value组合在key一起的原因,而写这个比较方法的时候,还要注意一定要符合Group方法的原因,如果group是按key的第一个参数来得,那这里key的比较就要在第一个参数相同的情况下,才能会第二个参数(value)进行比较,我想这里解释了为什么这种排序叫Secondary Sort的原因吧,在上面的例子中,key的比较是先比较第一个参数(年份),如果第一个参数相同,再比较第二个参数(温度),按第二个参数降序排列。

所以一般要使用Secondary Sort,在JobConf要配置这三个参数
setPartitionerClass // 这个是用来设置key的切分,上面例子中是按key中的第一个参数来切分
setOutputValueGroupingComparator // 这里设置group,就是按key的哪一个参数进行聚合,上面的例子中也是按第一个参数年份进行聚合
setOutputKeyComparatorClass // 这个是设置key的比较器,设置聚合的key的一个二次排序方法

3. 代码分析

Example 8-9. Application to find the maximum temperature by sorting temperatures in the key

public  class MaxTemperatureUsingSecondarySort  extends Configured  implements Tool  {  
   // Map任务  
   static  class MaxTemperatureMapper  extends MapReduceBase  implements Mapper <LongWritable, Text, IntPair, NullWritable >  {  
   private NcdcRecordParser parser  =  new NcdcRecordParser ( ) ;  
   public  void map (LongWritable key, Text value,  
      OutputCollector <IntPair, NullWritable > output, Reporter reporter )  
       throws  IOException  {  
    parser. parse (value ) ;    // 解析输入的文本  
     if  (parser. isValidTemperature ( ) )  {  
     // 这里把年份与温度组合成一个key,value为空  
      output. collect ( new IntPair (parser. getYearInt ( ), + parser. getAirTemperature ( ) ), NullWritable. get ( ) ) ;  
     }  
   }  
}  
// Reduce任务  
static  class MaxTemperatureReducer  extends MapReduceBase  
   implements Reducer <IntPair, NullWritable, IntPair, NullWritable >  {  
   public  void reduce (IntPair key, Iterator <NullWritable > values,  
      OutputCollector <IntPair, NullWritable > output, Reporter reporter )  
       throws  IOException  {  
     // 输出聚合的key值,这里的key是先按年份进行聚合,所我们会看到相同所有年份相同的key会聚合在一起,而这些聚合后的key按温度进行降序按列  
     // 所以聚合中第一个key为温度最高的,所以这里输出的key为这一年中温度最高的值  
    output. collect (key, NullWritable. get ( ) ) ;  
   }  
}  
  
// 切分器,这里是按年份* 127 % reduceNum来进行切分的  
public  static  class FirstPartitioner  
   implements Partitioner <IntPair, NullWritable >  {  
  @Override  
   public  void configure (JobConf job )  { }  
  @Override  
   public  int getPartition (IntPair key, NullWritable value,  int numPartitions )  {  
     return  Math. abs (key. getFirst ( )  *  127 )  % numPartitions ;  
   }  
}  
  
// 聚合key的一个比较器  
public  static  class KeyComparator  extends WritableComparator  {  
   protected KeyComparator ( )  {  
     super (IntPair. classtrue ) ;  
   }  
  @Override  
   public  int compare (WritableComparable w1, WritableComparable w2 )  {  
    IntPair ip1  =  (IntPair ) w1 ;  
    IntPair ip2  =  (IntPair ) w2 ;  
     // 这里要注意的是,一定要在聚合参数相同的情况下,再比较另一个参数  
     // 这里是先比较年份,再比较温度,按温度降序排序  
     int cmp  = IntPair. compare (ip1. getFirst ( ), ip2. getFirst ( ) ) ;  
     if  (cmp  !=  0 )  {  
       return cmp ;  
     }  
     return  -IntPair. compare (ip1. getSecond ( ), ip2. getSecond ( ) ) ;  //reverse  
   }  
}  
   // 设置聚合比较器  
   public  static  class GroupComparator  extends WritableComparator  {  
     protected GroupComparator ( )  {  
       super (IntPair. classtrue ) ;  
     }  
    @Override  
     public  int compare (WritableComparable w1, WritableComparable w2 )  {  
      IntPair ip1  =  (IntPair ) w1 ;  
      IntPair ip2  =  (IntPair ) w2 ;  
     // 这里是按key的第一个参数来聚合,就是年份  
       return IntPair. compare (ip1. getFirst ( ), ip2. getFirst ( ) ) ;  
     }  
   }  
  @Override  
   public  int run ( String [ ] args )  throws  IOException  {  
    JobConf conf  = JobBuilder. parseInputAndOutput ( this, getConf ( ), args ) ;  
     if  (conf  ==  null )  {  
       return  - 1 ;  
     }  
    conf. setMapperClass (MaxTemperatureMapper. class ) ;  
    conf. setPartitionerClass (FirstPartitioner. class ) ;      // 设置切分器  
    conf. setOutputKeyComparatorClass (KeyComparator. class ) ;  // 设置key的比较器  
    conf. setOutputValueGroupingComparator (GroupComparator. class ) ;  // 设置聚合比较器  
    conf. setReducerClass (MaxTemperatureReducer. class ) ;  
    conf. setOutputKeyClass (IntPair. class ) ;   // 设置key的一个组合类型,如里这个类型实现了WritableComparable<T>的话,那就不要设置setOutputKeyComparatorClass了.  
    conf. setOutputValueClass (NullWritable. class ) ;   // 输出的value为NULL,因为这里的实际value已经组合到了key中  
    JobClient. runJob (conf ) ;  
     return  0 ;  
   }  
  
   public  static  void main ( String [ ] args )  throws  Exception  {  
     int exitCode  = ToolRunner. run ( new MaxTemperatureUsingSecondarySort ( ), args ) ;  
     System. exit (exitCode ) ;  
   }  
}

输出结果如下:

% hadoop jar job.jar MaxTemperatureUsingSecondarySort input/ncdc/all
> output-secondarysort
% hadoop fs -cat output-secondarysort/part-* | sort | head
1901 317
1902 244
1903 289
1904 256
1905 283
1906 294
1907 283
1908 289

要注意的是有时候用户会存储这些value,所以你要在Map的时候输出这些value,而不是上面的NULL,而输出的这些value是经过排序的。

4 参考

http://www.riccomini.name/Topics/DistributedComputing/Hadoop/SortByValue/

Hadoop The definitive Guide Chapter 8

https://issues.apache.org/jira/secure/attachment/12356648/485.patch

https://issues.apache.org/jira/browse/HADOOP-4545

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值