切片
-
MapReduce 执行中是以mapTask 为单位对数据进行处理,
-
mapTask 的个数与操作数据段是由切片决定的
-
切片是在单个文件的基础上通过一些机制设定的
-
默认 MapReduce 使用 TextInputFormat 对数据进行读取分片, 切片大小默认为 block 大小
-
切片过大会造成按个 mapTask 执行时间长,不能充分利用 hadoop 集群 dataNode 多节点的特性; 切片过小会造成 由于启动 mapTask 本身需要一定的时间, 而实际处理数据时间很短暂, 造成的大量启动 mapTask 造成性能低下
-
大量小文件会对 MapReduce 造成性能影响
默认切片等于 block 大小, 当单个节点存储数据等于 block 时(源数据>=block, 分片存储在单个或多个节点), 一个节点会对一个文件生成一个 mapTask 执行数据操作,并且不会有数据传输的消耗
假设切片为 100M, 小于 block, 那么当单个节点存储数据等于 block 时(源数据>=block, 分片存储在单个或多个节点), mapTask 会在不同的dataNode 节点读取数据, 造成数据传输过程中的性能损耗
- 切片过程
- 程序找到数据存储的目录
- 开始遍历处理目录下的每个文件
- 遍历到第一个文件 f.txt
- 获取文件大小 fs.size(f.txt)
- 计算切片大小 computeSplitSize(Math.max(minSize, Math.min(maxSize,blocksize)))=blocksize=128M
- 默认切片大小=blocksize
- 开始切片, 0-128M, 128-256M, 256-300M(每次切片时,都要判断切完剩余部分是否大于block 的 1.1 倍, 不大于就划分为 1 块)
- 建切片信息记录到规划文件中(记录每个切片元数据:起止位置)
- 提交切片规划文件到 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
- 输入源目录下有多个文件读取文件大小
- 判断文件大小是否大于分片大小, 如果大于, 将文件划分为以filesize/(filesize/splitsize+(filesize%splitsize>0?1:0))大小相等的多个分片 例如 120>100 划分为两个 60
- 将划分后的分片组合, 判断第一个分片是否大于 splitsize, 不大于, 直接+上后一个分片的大小,此时如果大于 splitsize, 则划分为一个分片, 下面再划分新的分片,
- 上面最开始的 7 个文件最终划分为四个分片 130M 130M 110M 140M
-
MultipleInputs 多个输入, 为 MapReduce 操作多个 InputFormat 和 mapper
-
DBInputFormat 使用 jdbc 读取关系型数据库内的数据
自定义输入格式
-
继承 FileInputFormat抽象类(读文本文件, ), 必须实现一个必须实现的createRecordReader方法, 创建RecordReader
也可以继承其它类实现不同的数据读取: CompositeInputFormat, DBInputFormat, ComposableInputFormat等
-
可以选择重写 FileInputFormat 其它方法, 包括是否分片, 和如何分片逻辑
-
自定义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 {
}
}