Hadoop MR 数据切片与输入格式化

切片

  • MapReduce 执行中是以mapTask 为单位对数据进行处理,

  • mapTask 的个数与操作数据段是由切片决定的

  • 切片是在单个文件的基础上通过一些机制设定的

  • 默认 MapReduce 使用 TextInputFormat 对数据进行读取分片, 切片大小默认为 block 大小

  • 切片过大会造成按个 mapTask 执行时间长,不能充分利用 hadoop 集群 dataNode 多节点的特性; 切片过小会造成 由于启动 mapTask 本身需要一定的时间, 而实际处理数据时间很短暂, 造成的大量启动 mapTask 造成性能低下

  • 大量小文件会对 MapReduce 造成性能影响

在这里插入图片描述

默认切片等于 block 大小, 当单个节点存储数据等于 block 时(源数据>=block, 分片存储在单个或多个节点), 一个节点会对一个文件生成一个 mapTask 执行数据操作,并且不会有数据传输的消耗

在这里插入图片描述

假设切片为 100M, 小于 block, 那么当单个节点存储数据等于 block 时(源数据>=block, 分片存储在单个或多个节点), mapTask 会在不同的dataNode 节点读取数据, 造成数据传输过程中的性能损耗

  • 切片过程
    1. 程序找到数据存储的目录
    2. 开始遍历处理目录下的每个文件
    3. 遍历到第一个文件 f.txt
      1. 获取文件大小 fs.size(f.txt)
      2. 计算切片大小 computeSplitSize(Math.max(minSize, Math.min(maxSize,blocksize)))=blocksize=128M
      3. 默认切片大小=blocksize
      4. 开始切片, 0-128M, 128-256M, 256-300M(每次切片时,都要判断切完剩余部分是否大于block 的 1.1 倍, 不大于就划分为 1 块)
      5. 建切片信息记录到规划文件中(记录每个切片元数据:起止位置)
    4. 提交切片规划文件到 YARN 上, YARN 上的 MrAppMaster根据规划文件计算 mapTask 调度资源执行

输入格式

MapReduce 的切片过程是在 FileInputFormat 中实现的, FileInputFormat官方提供了多个实现类* *

  • TextInputFormat

    TextInputFormat是默认的 InputFormat. 每条记录是一行输入, 建是 LongWritable 类型, 是当前数据读取起始字节偏移量,值是当前数据的一行数据(不包含结束行符:回车/换行)

    hello!\n
    hello, zcz!\n
    hello world!\n
    

    上面数据会被划分为3条记录

    key  value
    0    hello!
    6    hello, zcz!
    17   hello world!
    

    Configuration可以通过设置LineRecordReader.MAX_LINE_LENGTH属性, 限制单行数据读取最大字节数, 若单行超过限制,表示数据异常, 不处理此行;

    conf.set(LineRecordReader.MAX_LINE_LENGTH, 2147483647);
    

    **注意: **文件是分块存储在不同节点的, 有可能一块数据的最后一行分在两个节点存储, 那么 mapTask 读取这一行时, 会存在跨节点读取数据, (虽然这种性能不会损耗很大, 但应注意, 一行数据非常大时, 可能传输数据就会很大消耗)

  • KeyValueTextInputFormat

    按行读取数据, 读取的数据可以根据指定分隔符, 读取出一行数据由这个分隔符分割出的第一个字符串为 key, 此行剩余部分为 value, 可以通过KeyValueLineRecordReader.KEY_VALUE_SEPERATOR指定分隔符

    java		hello java!
    scala		hello scala!
    python		hello python!
    ruby		hello ruby!
    golang		hello golang!
    php			hello php!
    
    conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, "\t");
    

    读取的内容为

    key			value
    java		hello java!
    scala		hello scala!
    python		hello python!
    ruby		hello ruby!
    golang		hello golang!
    php			hello php!
    
  • NLineInputFormat

    按照指定行数读取数据, 读到的数据 key 是字节偏移量, value 是读取到的所有行数据

    可以通过NLineInputFormat.LINES_PER_MAP设置读取的行数限制, 默认 1 行

    conf.set(NLineInputFormat.LINES_PER_MAP, 1);
    
  • SequenceFileInputFormat

    读取 SequenceFile 类型数据, key 和 value 由 SequenceFile 内部数据决定,它是存储二进制数据的, 并且是可压缩的, 内部可以维护 key-value. 后面通过学习 SequenceFile 补充;

    • SequenceFileAsTextInputFormat

      将读取SequenceFile的 key-value 转为 Text 类型

    • SequenceFileAsTextInputFormat

      将读取SequenceFile的 key-value 转为 BytesWritable 类型

  • FixLengthInputFormat

    用户从文件中读取固定宽度的二进制记录, 默认这些数据没有用分隔符分开,通过设置FixLengthInputFormat.FIXED_RECORD_LENGTH设置每个记录的大小

  • CombineTextInputFormat

    大量小文件情况下, 会对每一个小文件划为一个切片,导致每个小文件一个 mapTask, 造成 MapReduce 性能及其低下, 通过 CombineTextInputFormat 可以将多个小文件划分到一个切片, 提高 MapReduce 单次处理的文件数据量, 减少 MapReduce 启动停止的开销

    CombineTextInputFormat.setMaxInputSplitSize(job,102400000);// 100M
    
    file1.txt    	20M				20M					(20+50+60)M
    file2.txt		50M				50M					
    file2.txt		120M			60M
    								60M					(60+70)M
    file2.txt		70M				70M
    file2.txt		70M				50M					(50+60)M
    file2.txt		120M			60M
    								60M					(60+80)M
    file2.txt		80M				80M
    
    1. 输入源目录下有多个文件读取文件大小
    2. 判断文件大小是否大于分片大小, 如果大于, 将文件划分为以filesize/(filesize/splitsize+(filesize%splitsize>0?1:0))大小相等的多个分片 例如 120>100 划分为两个 60
    3. 将划分后的分片组合, 判断第一个分片是否大于 splitsize, 不大于, 直接+上后一个分片的大小,此时如果大于 splitsize, 则划分为一个分片, 下面再划分新的分片,
    4. 上面最开始的 7 个文件最终划分为四个分片 130M 130M 110M 140M
  • MultipleInputs 多个输入, 为 MapReduce 操作多个 InputFormat 和 mapper

  • DBInputFormat 使用 jdbc 读取关系型数据库内的数据

自定义输入格式

  1. 继承 FileInputFormat抽象类(读文本文件, ), 必须实现一个必须实现的createRecordReader方法, 创建RecordReader

    也可以继承其它类实现不同的数据读取: CompositeInputFormat, DBInputFormat, ComposableInputFormat等

  2. 可以选择重写 FileInputFormat 其它方法, 包括是否分片, 和如何分片逻辑

  3. 自定义RecordReader, 内部为读取每一组 key- value 的逻辑

/**
 * <h3>study-all</h3>
 *
 * <p></p>
 *
 * @Author zcz
 * @Date 2020-04-04 19:49
 */
public class CustomerInputFormat extends FileInputFormat {

    @Override
    public RecordReader createRecordReader(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        CustomerRecordReader customerRecordReader = new CustomerRecordReader();
        customerRecordReader.initialize(inputSplit, taskAttemptContext);
        return customerRecordReader;
    }
}
/**
 * <h3>study-all</h3>
 *
 * <p></p>
 *
 * @Author zcz
 * @Date 2020-04-04 19:53
 */
public class CustomerRecordReader extends RecordReader {

    //根据读取数据源类型 这里可以使用多种 split
    //CombineFileSplit
    //CompositeInputSplit
    //DBInputSplit
    //FileSplit
    private FileSplit split;
    private TaskAttemptContext context;
    //key和 value 都是事先 writable 的可序列化的对象, 根据具体业务需求, 判定 mapper 输入的 key-value 类型
    private Object key;
    private Object value;

    //初始化切片和上下文, 可以在这里对数据进行校验  初始化, 等逻辑
    @Override
    public void initialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        this.split = (FileSplit)inputSplit;
        this.context = taskAttemptContext;
    }

    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {
        //split 拿到的是当前整个分区数据, 根据具体业务, 读取分区数据内容,
        //根据实际业务逻辑从 split 中读数据, 记录读出数据的位置,
        //判定读取的数据还没有读取完, 返回 true 继续读
        //判定读取的数据已经读完了, 返回 false

        return false;
    }

    @Override
    public Object getCurrentKey() throws IOException, InterruptedException {
        //返回本次的 key 对应 mapper 输入的 key
        return key;
    }

    @Override
    public Object getCurrentValue() throws IOException, InterruptedException {
        //返回本地的 value 对应 mapper 输入的 value
        return value;
    }

    @Override
    public float getProgress() throws IOException, InterruptedException {
        //返回当前读取的进度
        return 0;
    }

    @Override
    public void close() throws IOException {

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值