MapReduce框架原理1
InputFormat数据输入
-
切片与MapTask并行度的决定机制
- 问题引出
①MapTask的并行度决定Map阶段的任务处理并发度,进而影响到整个Job的处理速度。
②思考:1G的数据,启动8个MapTask,可以提高集群的并发处理能力。那么1K的数据,也启动8个MapTask,会提高集群性能吗?MapTask并行任务是否越多越好呢?哪些因素影响了MapTask并行度 - MapTask并行度决定机制
①数据块:Block是HDFS物理上把数据分成一块一块。
②数据切片:数据切片只是在逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。- 一个job的Map端的并行度由提交Job时的切片数决定
- 每一个split切片分配一个MapTask并行实时处理
- 默认情况下,切片大小=BlockSize
- 切片时不考虑数据集整体,而是对每一个文件进行切片处理
- 问题引出
-
Job提交源码和切片源码详解:
- Job提交源码分析:
//waitForCompletion()
// 1 建立连接
connect();
// 1.1 创建提交Job的代理
new Cluster(getConfiguration());
// 1.1.1 判断是本地yarn还是远程
initialize(jobTrackAddr, conf);
// 2 提交job
submitter.submitJobInternal(Job.this, cluster)
// 2.1 创建给集群提交数据的Stag路径
Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
// 2.2 获取jobid ,并创建Job路径
JobID jobId = submitClient.getNewJobID();
// 2.3 拷贝jar包到集群
copyAndConfigureFiles(job, submitJobDir);
rUploader.uploadFiles(job, jobSubmitDir);
// 2.4 计算切片,生成切片规划文件
writeSplits(job, submitJobDir);
maps = writeNewSplits(job, jobSubmitDir);
input.getSplits(job);
// 2.5 向Stag路径写XML配置文件
writeConf(conf, submitJobFile);
conf.writeXml(out);
// 2.6 提交Job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());
Job提交流程分析图:
FileInputFormat切片源码分析:
-
FileInputFormat切片机制
- 切片机制
- 简单的按照文件内容的长度切片
- 默认情况下,切片大小=BlockSize
- 切片时不考虑数据集整体,而是对每一个文件进行切片处理
- 若有两个文件a.txt(300M)、b.txt(10M)经过FileInputFormat切片机制运算后,形成的切片信息为:
a.txt.split1-- (0-128M)、
a.txt.split2-- (128-256M)、
a.txt.split3-- (256-300M)、
b.txt.split1-- (0-10M)
- FileInputFormat切片大小参数配置:
- 源码中计算切片大小的公式:
Math.min(minSize,Math.max(maxSize,blockSize))
mapreduce.input.fileinputformat.split.minsize = 1//默认值为1
mapreduce.input.fileinputformat.split.maxsize = Long.MAXValue//默认值为Long.MAXValue
- 切片大小设置:
①maxsize: 若设置的比blocksize小,则会让切片变小
②minsize:设置的比blocksize大,会让切片变得比blocksize大 - 获取切片信息API:
String name = inputSplit.getPath().getName();//获取切片文件名称
FileSplit inputSplit = (FileSplit)context.getInputSplit();//根据文件类型获取切片信息
- 源码中计算切片大小的公式:
- 切片机制
-
CombineTextInputFormat切片机制
-
框架默认的TextInputFormat切片机制是对任务按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个MapTask,这样如果有大量小文件,就会产生大量的MapTask,处理效率极其低下。
-
应用场景:
CombineTextInputFormat用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切 片中,这样,多个小文件就可以交给一个MapTask处理。 -
虚拟存储切片最大值设置
CombineTextInputFormat.setMaxInputSplitSize(job,4194304);// 4M
注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值。 -
切片机制
生成切片过程包括:虚拟存储过程和切片过程二部分:-
虚拟存储过程:
①将输入目录下所有文件大小,依次和设置的setMaxInputSplitSize值比较,如果不大于设置的最大值,逻辑上划分一个块。如果输入文件大于设置的最大值且大于两倍,那么以最大值切割一块;当剩余数据大小超过设置的最大值且不大于最大值2倍,此时将文件均分成2个虚拟存储块(防止出现太小切片)。
②例如:setMaxInputSplitSize值为4M,输入文件大小为8.02M,则先逻辑上分成一个4M。剩余的大小为4.02M,如果按照4M逻辑划分,就会出现0.02M的小的虚拟存储文件,所以将剩余的4.02M文件切分成(2.01M和2.01M)两个文件 -
切片过程:
①判断虚拟存储的文件大小是否大于setMaxInputSplitSize值,大于等于则单独形成一个切 片
②如果不大于则跟下一个虚拟存储文件进行合并,共同形成一个切片。
③测试举例:有4个小文件大小分别为1.7M、5.1M、3.4M以及6.8M这四个小文件,则虚拟存储之后形成6个文件块,大小分别为:
1.7M,(2.55M、2.55M),3.4M以及(3.4M、3.4M)
最终会形成3个切片,大小分别为:
(1.7+2.55)M,(2.55+3.4)M,(3.4+3.4)M
-
-
-
FileInputFormat实现类
针对不同的数据类型(日志文件,二进制文件,数据库表等),需要不同FileInputFormat实现类来满足需求,FileInputFormat常见的实现类包括:TextInputFormat、KeyValueTextInputFormat、NLineInputFormat、CombineTextInputFormat和自定义InputFormat等-
TextInputFormat
-
KeyValueTextInputFormat
-
NLineInputFormat
-
-
自定义InputFormat案例实操
-
无论HDFS还是MapReduce,在处理小文件时效率都非常低,但又难免面临处理大量小文件的场景,此时,就需要有相应解决方案。可以自定义InputFormat实现小文件的合并。
-
需求:
将多个小文件合并成一个SequenceFile文件(SequenceFile文件是Hadoop用来存储二进制形式的key-value对的文件格式),SequenceFile里面存储着多个文件,存储的形式为文件路径+名称为key,文件内容为value。 -
需求分析:
- 自定义一个类继承FileInputFormat:
①重写isSplitable()方法,返回false不可分割
②重写createRecordReader()
③创建自定义RecordReader对象并初始化 - 改写RecordReader,实现一次读取一个完整文件封装为KV
①采用IO流读取一个文件输出到value中,因为设置了不可切片,最终把所有文件封装到了 value中
②获取文件路径信息+名称,设置为key - 设置Driver
// 1 设置输入的inputformat job.setInputFormatClass(WholeFileInputFormat.class) // 2 设置输出的outputformat job.setOutputFormatClass(SecquenceFileOutputFormat.class)
- 自定义一个类继承FileInputFormat:
-
程序实现:
①WholeRecordReader类
-
public class WholeRecordReader extends RecordReader {
private Configuration configuration;
private FileSplit split;
private boolean isProgress = true;
private BytesWritable v = new BytesWritable();
private Text k = new Text();
@Override
public void initialize(InputSplit inputSplit, TaskAttemptContext context) throws IOException, InterruptedException {
this.split = (FileSplit) inputSplit;
configuration = context.getConfiguration();
}
@Override
public boolean nextKeyValue() {
if (isProgress) {
// 1 定义缓冲区
byte[] contents = new byte[(int) split.getLength()];
FileSystem fs = null;
FSDataInputStream fis = null;
try {
// 2 获取文件系统
Path path = split.getPath();
fs = path.getFileSystem(configuration);
// 3 读取数据
fis = fs.open(path);
// 4 读取文件内容
IOUtils.readFully(fis, contents, 0, contents.length);
// 5 输出文件内容
v.set(contents, 0, contents.length);
// 6 获取文件路径及名称
String name = split.getPath().toString();
k.set(name);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeStream(fis);
}
isProgress = false;
return true;
}
return false;
}
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return k;
}
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return v;
}
@Override
public float getProgress() throws IOException, InterruptedException {
return 0;
}
@Override
public void close() throws IOException {
}
}
②WholeFileInputFormat类
// 定义类继承FileInputFormat
public class WholeFileInputFormat extends FileInputFormat<Text,BytesWritable>{
@Override
protected boolean isSplitable(JobContext context, Path filename) {
return false;
}
@Override
public RecordReader<Text, BytesWritable> createRecordReader(InputSplit inputSplit, TaskAttemptContext context) throws IOException, InterruptedException {
WholeRecordReader recordReader = new WholeRecordReader();
recordReader.initialize(inputSplit,context);
return recordReader;
}
}
③SequenceFileMapper类
public class SequenceFileMapper extends Mapper<Text, BytesWritable, Text, BytesWritable> {
@Override
protected void map(Text key, BytesWritable value, Context context) throws IOException, InterruptedException {
context.write(key, value);
}
}
④SequenceFileReducer类
public class SequenceFileReducer extends Reducer<Text, BytesWritable, Text, BytesWritable> {
@Override
protected void reduce(Text key, Iterable<BytesWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,values.iterator().next());
}
}
⑤SequenceFileDriver类:
public class SequenceFileDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 输入输出路径需要根据自己电脑上实际的输入输出路径设置
args = new String[] { "e:/input/inputinputformat", "e:/output1" };
// 1 获取job对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 设置jar包存储位置、关联自定义的mapper和reducer
job.setJarByClass(SequenceFileDriver.class);
job.setMapperClass(SequenceFileMapper.class);
job.setReducerClass(SequenceFileReducer.class);
// 3 设置map输出端的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(BytesWritable.class);
// 4 设置最终输出端的kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
// 5 设置输入的inputFormat
job.setInputFormatClass(WholeFileInputformat.class);
// 6 设置输出的outputFormat
job.setOutputFormatClass(SequenceFileOutputFormat.class);
// 7 设置输入输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 8 提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}