MR的分片、输入输出格式 ——Hadoop权威指南13

1. 输入分片与记录

1. 文件 → \rightarrow split → \rightarrow record的转化
  • map任务的个数,取决于split数,split对应的Java接口为InputSplit

  • InputSplit包含一个以字节为单位length、一组存储位置,但本身不包含数据本身,而是包含指向数据的引用

    public abstract long getLength() throws IOException, InterruptedException;
    
    public abstract 
        String[] getLocations() throws IOException, InterruptedException;
    
  • InputSplit不需要用户自己去处理,而是由InputFormat提供支持

  • InputFormat负责创建split并将split分割出记录

    // 计算出文件的split
    public abstract List<InputSplit> getSplits(JobContext context)
    	throws IOException, InterruptedException;
    // 为split创建RecordReader
    public abstract RecordReader<K,V> createRecordReader(InputSplit split, TaskAttemptContext context) 
    	throws IOException, InterruptedException;
    
  • RecordReader就像一个迭代器,可以从split中分割出记录,并将记录传递给map()函数进行处理。对应的

    public void run(Context context) throws IOException, InterruptedException {
      setup(context);
      try { 
        while (context.nextKeyValue()) {
          map(context.getCurrentKey(), context.getCurrentValue(), context);
        }
      } finally {
        cleanup(context);
      }
    }
    
  1. context.nextKeyValue()返回true,表示存在记录。记录通过context.getCurrentKey()context.getCurrentValue()进行获取,然后传递给map()函数
  2. context.nextKeyValue()返回false,表示没有记录了,便执行cleanup(context)
  3. 注意: context.getCurrentKey()context.getCurrentValue()返回的是同一个对象,只是对象的值发生了变化
1.2 FileInputFormat
  • FileInputFormat是文件数据源的InputFormat的基类,提供两个功能
  1. 指出作业的输入文件位置
  2. 计算输入文件的split,即getSplits()
    在这里插入图片描述

指出作业的输入文件位置:

  • FileInputFormat可以设置输入文件的位置,路径可以是文件、目录、glob。

  • 如果路径是目录,则表示目录下的所有文件都是作业的输入

    // 设置文件的输入位置
    public static void addInputPath(Job job, Path path) throws IOException
    public static void addInputPaths(Job job, String commaSeparatedPaths) throws IOException
    public static void setInputPaths(Job job, Path... inputPaths) throws IOException
    public static void setInputPaths(Job job, Path... inputPaths) throws IOException
    
  • FileInputFormat还可以设置过滤器,以排除特定的文件

    public static void setInputPathFilter(Job job, Class<? extends PathFilter> filter)
    
1.3 split大小的计算
  • FileInputFormat通过以下公式,计算split大小,从而创建文件的split
    m a x ( m i n S i z e , m i n ( m a x S i z e , b l o c k S i z e ) ) max(minSize, min( maxSize, blockSize)) max(minSize,min(maxSize,blockSize))
  • 相关属性信息如下:
    在这里插入图片描述
  • 注意: 在旧版本的MR API中,没有maxSize。它的值是由fileSize / mapTaskNum计算得到的
  • 默认情况下,使得split大小就是blockSize
    m i n S i z e < b l o c k S i z e < m a x S i z e minSize < blockSize < maxSize minSize<blockSize<maxSize
    相对于blockSize,如何创建更大或更小的split?
  1. split size想要大于blockSize,可以将minSize设置成比blockSize更大的值
  2. split size想要小于blockSize,可以将maxSize设置成比blockSize更小的值
  • 《Hadoop编程指南》,还给出了一些具体的示例,告诉读者如何控制split大小
1.4 实际情况的处理

输入文件,是大量的小文件

  • 如果输入文件是大量的小文件,即文件size小于blockSize,则每个文件将对应的一个map任务
  • 方法一:提前使用顺序文件将小文件进行合并
  • 方法二:使用CombineFileInputFormat,可以将多个文件打包到一个分片中,从而避免创建大量的map任务

不需要split文件

  • 考虑这样的场景:检查一个文件中的key是否有序,则不能将文件进行split,而是需要由一个map任务处理整个文件

  • 方法一:将split大小设置为文件大小,避免文件被split

  • 方法二:使用FileInputFormat的子类,重写isSplitable()方法,将其返回值设置为false,则不会对文件进行split

    protected boolean isSplitable(JobContext context, Path filename) {
        return false;
      }
    

不需要分割出记录

  • 考虑这样的场景:需要将整个文件的内容作为value进行处理,即不能使用原生的RecordReader对文件内容进行分割
  • 方法:
  1. 自定义WholeFileInnputFormat类:重写isSplitable()方法,将其返回设置为false;createRecordReader(),返回是的可以读取整个文件内容的、自定义的RecordReader
  2. 自定义RecordReader,将整个文件作为一条记录返回

2. 各种输入输出格式

2.1 TextInputFormat/TextOutputFormat

TextInputFormat

  1. TextInputFormat实默认的InputFormat,每条记录是一行输入
  2. key:LongWritable,该行数据开始处在文件中的offset
  3. value:Text,该行的内容,不包括终止符(换行符合回车符)
  4. 关于一行数据跨block的问题:
    (1)一个文件包含若干行,按照blockSize存储文件时,块边界与行边界可能并未对齐;
    (2)FileInputFormat创建split边界与行边界对齐,即split和行数据都可能会跨块
    (3)这时,本地化的map任务需要执行一些远程读操作,但带来的额外开销并不是十分明显
    在这里插入图片描述5. 总结起来:
    • block是HDFS的存储单位,是对文件的真实分割
    • split是MR对文件进行处理的输入单位,只是一个逻辑概念,并未对文件做分割
    • 由size(由start和length决定)、数据的存储位置来决定一个split的具体内容

KeyValueTextInputFormat

  • TextInputFormat其key-value已经固定,对于很多文件来说并不适用
  • 通常情况下,文件的一行数据,包含key和value,key、value之间通过分隔符进行分隔
  • 这时,可以通过KeyValueTextInputFormat进行处理
  • KeyValueTextInputFormat的默认分隔符为\t,也可以通过属性进行指定

TextOutputFormat

  • 默认的OutputFormatTextOutputFormat,将一条记录写为文本行
  • TextOutputFormat需要指定key-value的数据类型,其创建的输出文件,可以由KeyValueTextInputFormat进行处理
  • TextOutputFormat默认分隔符为\t,可以通过属性进行指定
  • 如果 TextOutputFormat的key设置为NullWritable,会导致记录中无key和分隔符,直接是value
  • 此时,创建的输出文件,可以由TextInputFormat处理
2.2 数据库输入输出

DBInputFormat

  • DBInputFormat用于通过JDBC从关系型数据库读取数据
  • 如果存在太多的map任务从数据库中读取数据,可能会是数据库高负载
  • 因此,DBInputFormat更加适合从数据库中国读取小量数据

DBOutputFormat

  • DBInputFormat对应的是,DBOutputFormat
  • DBOutputFormat可以将作业输出(中等规模数据)转储到数据库中

专为HBase提供的输入输出格式

  • HBase专用的输入格式为:TableInputFormat,用于MR作业操作HBase中的数据
  • HBase专用的输出格式为TableOutputFormat,用于将MR的输出写到HBase

3. 总结

  • 文件 → \rightarrow split → \rightarrow key-value记录的整个流程

    1. 重点方法:getSplits()createRecordReader()run()
    2. InputSplit接口,提供获取文件长度、存储位置的方法,本身不包含数据,而是指向数据的引用
  • split大小的计算:

    1. max (minSize, min(maxSize, blockSize),以及旧版MR API中,maxSize的计算
    2. 如何增加或减少split的大小
  • 实际情况的处理:

    1. 输入是大量的小文件:要么使用顺序文件合并小文件,要么使用CombineInputFormat,将多个小文件分配到一个split
    2. 不对文件进行split:重写isSplitable(),将其返回值设为false
    3. 将整个文件当做一条记录:自定义WholeFileInputFormat(重写isSplitable(),返回自定义的RecordReader)、自定义RecordReader(只返回一条记录)
  • 常见的文本输入输出格式:TextInputFormat/KeyValueTextInputFormatTextOutputFormat

  • 常见的数据库输入输出格式:

    1. 关系型数据库:DBInputFormatDBOutputFormat
    2. HBase:TableInputFormatTableOutputFormat
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值