一、框架原理
MapReduce的数据流
数据如何进行分份?
如果一份文件300M,若平均分配,会引起网络传输,而Shuffle阶段非常依赖于网络带宽,这一阶段又很重要,直接决定了MapReduce程序的性能,所以为了提高Shuffle的性能,会尽量将网络带宽交给shuffle来用,其他时间一般不要使用网络传输,所以如果按照块大小的128M来切,会大大提高任务的性能。
任务的切片是在Yarn的客户端完成,也就是Driver。
任务提交源码:
job提交中做了什么事情:检查job的状态(DEFINE),设置新的API,连接集群,向集群提交job(检查job的输出,获取临时文件夹,获取job的ID,向临时文件夹中写jar包,写切片信息,写配置文件,最终提交任务,结束)
切片过程:切片发生在默认的任务提交过程中,根据切片的数量启动map的数量,如何切片:获取具体job的配置信息,从job的配置中获取inputFormatClass(默认为TestInputFormat),根据类对象,通过反射的方式生成InputFormatFormat对象,调用InputFormat对象的getSplits方法来切片(计算minSize和maxSize默认为1和long的最大值,然后开始生成切片,使用ArrayList存放切片,获取输入目录的文件列表,遍历文件,并且对每个文件进行切片,如果是目录则不处理,获取文件路径,获取文件长度,如果长度为0则不切片,判断文件是否可以进行切片【如果是文本文件,一定可以进行切片,如果是压缩文件,要判断压缩编码是否支持切片】,获取文件的块大小,根据BlockSize,minSize,maxSize三者判断切片的大小【大小判断规则:max(minSize,min(maxSize,BlockSize))】,记录当前文件还有多长没有被切片记为bytesRemaining,当bytesRemaing / splitSize > 1.1时可以切片【防止map的浪费,如大于1,则128.1M的文件会被切成两片,现在当剩余文件小于128M + 12.8M时,不会再启动一个map,节省资源】),将切片的结果放在list中,之后将list换成数组,将数组信息写出去,返回数组的长度。
InputFormat类是所有InputFormat的抽象父类,工作之一是将输入进来的文件从逻辑上划分成几个set,第二个工作是对每一个切片创建一个record reader(切片在客户端完成,客户端调用InputFormat,将数据切分成很多片,此时的片还不是KV值,每一个切片分配一个MapTask进行处理,创建record reader的过程发生在MapTask中),record reader把数据打碎成KV值输入给Mapper。
CombineTextInputFormat切片机制:用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样多个小文件就可以交给一个MapTask处理。
自定义InputFormat:需求,将整个文件转化成KV对。
自定义几个小文件(大于三个),将这几个小文件转化成一个KV文件。
MyInputFormat类
package com.hike.mr.inputformat;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import java.io.IOException;
public class MyInputFormat extends FileInputFormat<Text, BytesWritable> {
/**
* 返回一个自定义的RecordReader
* @param inputSplit
* @param taskAttemptContext
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public RecordReader<Text, BytesWritable> createRecordReader(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
return new MyRecordReader();
}
}
MyRecordReader类
package com.hike.mr.inputformat;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.logging.log4j.message.ReusableMessage;
import java.io.IOException;
/**
* 负责将整个文件转化成一组KV对
*/
public class MyRecordReader extends RecordReader<Text, BytesWritable> {
//表示文件读完了,默认为false,表示文件没读过
private boolean isRead = false;
//KV对
private Text key = new Text();
private BytesWritable value = new BytesWritable();
private FSDataInputStream inputStream;
private FileSplit fs;
/**
* 初始化方法,一般执行一些初始化操作
* @param inputSplit
* @param taskAttemptContext
* @throws IOException
* @throws InterruptedException
*/
@Override
public void initialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
//开流
fs = (FileSplit) inputSplit; //转化成子类,inputSplit是最抽象的方法
FileSystem fileSystem = FileSystem.get(taskAttemptContext.getConfiguration()); //获取配置信息,不要new,因为Driver中已经new过了
inputStream = fileSystem.open(fs.getPath()); //转化成子类才能够获取到路径,父类是无法获取路径的
}
/**
* 读取下一个指针指向的KV对
* @return 读到了返回false,没读到返回true,因为读到,指针到了最后,再读就没有值了,所以返回false
* @throws IOException
* @throws InterruptedException
*/
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
if(!isRead){
//读取文件
//填充K
key.set(fs.getPath().toString());
// 填充V
byte[] buffer = new byte[(int) fs.getLength()];
inputStream.read(buffer);
value.set(buffer,0,buffer.length);
//标记文件读完
isRead = true;
return true;
} else {
return false;
}
}
/**
* 获取当前读到的key
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return key;
}
/**
* 获取当前读到的value
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return value;
}
/**
* 显示已经完成了多少数据,是一个0.0 - 1.0 的数字
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public float getProgress() throws IOException, InterruptedException {
return isRead ? 1 : 0;
}
/**
* 关闭方法,一般用来关闭资源
* @throws IOException
*/
@Override
public void close() throws IOException {
//关流
IOUtils.closeStream(inputStream);
}
}
MyInputDriver类
package com.hike.mr.inputformat;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import java.io.IOException;
public class MyInputDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Job job = Job.getInstance(new Configuration());
job.setJarByClass(MyInputDriver.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(BytesWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
job.setInputFormatClass(MyInputFormat.class);
job.setOutputFormatClass(SequenceFileOutputFormat.class);
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
自定义的InputFormat插件,改变了程序的InputFormat。