【Hadoop】30-边数据分布

“边数据”(side data)是作业所需的额外的只读数据,以辅助处理主数据集。所面临的挑战在于如何使所有map或reduce任务(这些任务散布在集群内部)都能够方便而高效地使用边数据。

1、利用JobConf来配置作业

Configuration类(或者旧版MapReduceAPI的JobConf类)的各种setter方法能够方便地配置作业的任一键·值对。如果仅需向任务传递少量元数据则非常有用。
在任务中,用户可以通过Context类的getConfiguration()方法获得配置信息。(在旧版API中,做法更加复杂一点:需要重写Mapper或者Reducer类的configure()方法,并调用传人〕obConf对象的getter方法。通常情况下,可将数据以实例字段的形式保存,使得其可在者reduce()方法中使用。)
一般情况下,基本类型足以应付元数据编码。但对于更复杂的对象,用户要么自己处理序列化工作(这需要实现一个对象与字符串之间的双向转换机制),要么使用Hadoop提供的Stringifier类。DefaultStringifier使用Hadoop的序列化框架来序列化对象。详情参见5.3节。
但是这种机制会加大MapReduce组件的内存开销压力,因此,并不适合传输多达几千字节的数据量。作业配置总是由客户端、applicationmaster和任务JVM读取。每次读取配置时,所有项都被读人到内存(即使暂时不用的属性项也不例外)。

2、分布式缓存

与在作业配置中序列化边数据的技术相比,Hadoop的分布式缓存机制更受青睐,它能够在任务运行过程中及时地将文件和存档复制到任务节点以供使用。为了节约网络带宽,在每一个作业中,各个文件通常只需要复制到一个节点一次。

2.1、用法

对于使用GenericOptionsParser(本书中多处程序均用到该类,参见6.2.2节的相关讨论)的工具来说,用户可以使用-files选项指定待分发的文件,文件内包含以逗号隔开的URI列表。文件可以存放在本地文件系统、HDFS或其他Hadoop可读文件系统(例如S3)之中。如果尚未指定文件系统,则这些文件被默认是本地的。即使默认文件系统并非本地文件系统,这也是成立的。
用户可以使用-archives选项向自己的任务中复制存档文件(JAR文件、ZIP文件、tar文件和gzippedtar文件),这些文件会被解档到任务节点。-libjars选项会把JAR文件添加到mapper和reducer任务的类路径中。如果作业JAR文件并没包含库JAR文件,这点会很有用。
以下指令显示如何使用分布式缓存来共享元数据文件,从而得到气象站的名称:

%hadoop jar hadoop-examples.jar MaxTemperatureByStationNameUsinOistributedCacheFile \
-files input/ncdc/metadata/stations-fixed-width.txt input/ncdc/alloutput

该命令将本地文件stations-fixed-width.txt(未指定文件系统,从而被自动解析为本地文件)复制到任务节点,从而可以查找气象站名称。范例9-13描述了类MaxTemperatureByStationNameUsingDistributedCacheFile的代码。
范例9-13.查找各气象站的最高气温并显示气象站名称,气象站文件是一个分布式缓存文件

public class MaxTemperatureByStationNameUsingDistributedCacheFile extends Configured implements Tool {
  static class StationTemperatureMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    private NcdcRecordParser parser = new NcdcRecordParser();
    @Override
    protected void map(LongWritable key, Text value, Context context)
        throws IOException, InterruptedException {
      
      parser.parse(value);
      if (parser.isValidTemperature()) {
        context.write(new Text(parser.getStationId()),
            new IntWritable(parser.getAirTemperature()));
      }
    }
  }
  
  static class MaxTemperatureReducerWithStationLookup extends Reducer<Text, IntWritable, Text, IntWritable> {
    private NcdcStationMetadata metadata;
    @Override
    protected void setup(Context context)
        throws IOException, InterruptedException {
      metadata = new NcdcStationMetadata();
      metadata.initialize(new File("stations-fixed-width.txt"));
    }

    @Override
    protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
      /*[*/String stationName = metadata.getStationName(key.toString());/*]*/
      int maxValue = Integer.MIN_VALUE;
      for (IntWritable value : values) {
        maxValue = Math.max(maxValue, value.get());
      }
      context.write(new Text(/*[*/stationName/*]*/), new IntWritable(maxValue));
    }
  }

  @Override
  public int run(String[] args) throws Exception {
    Job job = JobBuilder.parseInputAndOutput(this, getConf(), args);
    if (job == null) {
      return -1;
    }
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    job.setMapperClass(StationTemperatureMapper.class);
    job.setCombinerClass(MaxTemperatureReducer.class);
    job.setReducerClass(MaxTemperatureReducerWithStationLookup.class);
    return job.waitForCompletion(true) ? 0 : 1;
  }
  
  public static void main(String[] args) throws Exception {
    int exitCode = ToolRunner.run(
        new MaxTemperatureByStationNameUsingDistributedCacheFile(), args);
    System.exit(exitCode);
  }
}

该程序通过气象站查找最高气温,因此mapper(StationTemperatureMapper)仅仅输出(气象站ID,气温)对。对于combiner,该程序重用MaxTemperatureReducer(参见第2章和第6章)来为map端的map输出分组获得最高气温。reducer(MaxTemperatureReducerWithStationLookup)则有所不同,它不仅要查找最高气温,还需要根据缓存文件查找气象站名称。
该程序调用reducer的setup()方法来获取缓存文件;输人参数是文件的原始名称,文件的路径与任务的工作目录相同。
当文件无法整个放到内存中时,可以使用分布式缓存进行复制。由于充当了一种在盘检索格式(参见5.4.2节),Hadoopmap文件在这方面非常有用。由于map文件是一组已定义目录结构的文件,用户可以将这些文件整理成存档格式(JAR、ZIP、或zipped tar),再用-archives选项将其加人缓存。
以下是输出的小片段,显示部分气象站的最高气温值。

2.2、工作机制

当用户启动一个作业,Hadoop会把由-files、-archives和-libjars等选项所指定的文件复制到分布式文件系统(一般是HDFS)之中。接着,在任务运行之前,节点管理器将文件从分布式文件系统复制到本地磁盘(缓存)使任务能够访问文件。此时,这些文件就被视为“本地化”了。从任务的角度来看,这些文件就已经在那儿了,以符号链接的方式指向任务的工作目录。此外,由-libjars指定的文件会在任务启动前添加到任务的类路径(classpath)中。节点管理器为缓存中的文件各维护一个计数器来统计这些文件的被使用情况。当任务即将运行时,该任务所使用的所有文件的对应计数器值增1;当任务执行完毕之后,这些计数器值均减1。仅当文件不在使用中(此时计数达到0),才有资格删除。当节点缓存的容量超过一定范围(默认10GB)时,需要根据最近最少使用原则删除文件以腾出空间来装载新文件。缓存的大小可以通过属性yarn.nodemanager.localizer.cache.target-size-mb来配置。
尽管该机制并不确保在同一个节点上运行的同一作业的后续任务肯定能在缓存中找到文件,但是成功的概率相当大。原因在于作业的多个任务在调度之后几乎同时开始运行,因此,不会有足够多的其他作业在运行而导致原始任务的文件从缓存中被删除。

2.3、分布式缓存API

由于可以通过GenericOptionsParser间接使用分布式缓存(如范例9-13所示),大多数应用不需要使用分布式缓存API。然而,如果没有使用GenencOptionsParser,那么可以使用Job中的API将对象放进分布式缓存中。以下是Job中相关的方法:

  • public void addCacheFile(URI uri)
  • public void addCacheArchive(URI uri)
  • public void setCacheFiles(URI[] lfiles)
  • public void setcacheArchives(URI[] archives)
  • public void addFileToClassPath(Path file)
  • public void addArchiveToClassPath(Path archive)
  • public void createSymlink()

在缓存中可以存放两类对象:文件(files)和存档(achives)。文件被直接放置在任务节点上,而存档则会被解档之后再将具体文件放置在任务节点上。每种对象类型setCacheXXXXs()和都包含三种方法:addCacheXXXX()addXXXXToClassPath()。其中,addCachexx()方法将文件或存档添加到分布式缓存,setcacheXXXXs()方法将一次性向分布式缓存中添加一组文件或存档(之
前调用所生成的集合将被替换),addXXXXToClassPath()方法将文件或存档添加到MapReduce任务的类路径。表9-7对上述API方法与GenericOptionsParser选项(参见表6-1)做了一个比较。

表9-7、分布式缓存API
Job的API名称GenericOptionsParser的等价选项说明

addCacheFile(URI uri)

setCacheFiles(URI[] files)

-files file1,file2,...将文件添加到分布式缓存,以备将来被复制到任务节点

addCacheArchive(URI uri)

setCacheArchives(URI[] files)

-archives archive1,archive2,...将存档添加到分布式缓存,以备将来被复制到任务节点,并在节点解档
addFileToClassPath(Path file)-libjars jar1,jar2,...将文件添加到分布式缓存,以备将来被复制到MapReduce任务的类路径中。文件并不会被解档,因此适合向类路径添加JAR文件
addArchiveToClassPath(Path archive)将存档添加到分布式缓存,以备将来解档、添蜘R的类路径中。当想向类路径添加目录和文件时,这种方式比较有用,因为用户可以创建一个包含指定文件的存档。此外,用户也可以创建一个JAR文件,并使用addFileToClassPath(),效果相同

add和set方法中的输人参数URI是指在作业运行时位于共享文件系统中的(一组)文件。而GenericOptionsParser选项(例如-files)所指定的文件可以是本地文件,如果是本地文件的话,则会被复制到默认的共享文件系统(一般是HDFS)。
这也是使用JavaAPI和使用GenericOptionsParser的关键区别:JavaAPI的add和方法不会将指定文件复制到共享文件系统中,但GenericOptionsParser会这样做。
从任务中获取分布式缓存文件在工作机理上和以前是一样的:通过名称访问本地化的文件,如范例9-13中所示。之所以起作用,是因为MapReduce总会在任务的工作目录和添加到分布式缓存中的每个文件或存档之间建立符号化链接。存档被解档后就能使用嵌套的路径访问其中的文件。
 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值