MapReduce 框架原理:
1、流程示意图:
2、流程详细:
上面的流程是整个 mapreduce 最全工作流程,但是 shuffle 过程只是从第 7 步开始到第 15 步结束,具体 shuffle 过程详解,如下:
1)maptask 收集我们的 map() 方法输出的 KV 对,放到内存缓冲区中
2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
3)多个溢出文件会被合并成大的溢出文件
4)在溢出过程中,及合并的过程中,都要调用 partitioner 进行分区和针对 key 进行排序
5)reducetask 根据自己的分区号,去各个 maptask 机器上取相应的结果分区数据
6)reducetask 会取到同一个分区的来自不同 maptask 的结果文件,reducetask 会将这些文件再进行合并(归并排序)
7)合并成大文件后,shuffle 的过程也就结束了,后面进入 reducetask 的逻辑运算过程(从文件中取出一个一个的键值对 group,调用用户自定义的 reduce() 方法)
3、注意:
shuffle 中缓冲区大小会影响到 mapreduce 程序的执行效率,原则上说,缓冲区越大,磁盘 io 的次数越少,执行速度就越快。
缓冲区的大小可以通过参数调整,参数:io.sort.mb 默认 **100M **。
InputFormat 数据输入(切片):
1、切片计算公式
1)找到里数据存储的目录。
2)开始遍历处理(规划切片)目录下的每个文件
3)遍历每个文件 ss.txt
(1)获取文件大小 fs.sizeOf(ss.txt);
(2)计算切片大小
computeSplitSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M
(3)默认情况下,切片大小=blocksize
(4)开始切,形成第 1 个切片:ss.txt — 0M:128M 第 2 个切片 ss.txt — 128M:256M 第 3 个切片 ss.txt — 256M:300M(每次切片时,都要判断切完剩下的部分是否大于快的 1.1 倍,不大于 1.1 倍就划分一块切片)
(5)将切片信息写到一个切片规划文件中。
(6)整个切片的核心过程在 getSplit() 方法中完成。
(7)数据切片只是在逻辑上对输入数据进行分片,并不会在磁盘上将其切分成分片进行存储。InputSplit 只记录了分片的元数据信息,比如起始位置、长度以及所在的节点列表等。
(8)注意:block 是 HDFS 物理上存储的数据,切片是对数据逻辑上的划分。
4)提交切片规划文件到 yarn 上,yarn 上的 MrAppMaster 就可以根据切片规划文件计算开启 maptask 个数。
自定义 Partition(分区) :
在前面代码的基础上增加 WordCountPartitioner 类:
/**
* 根据数字的第一位的奇偶数进行分区
* @author Jds
*/
public class WordCountPartitioner extends Partitioner<Text, IntWritable> {
@Override
public int getPartition(Text key,
IntWritable value,
int i) {
String num = key.toString().substring(0, 1);
// String --> Int
int result = Integer.valueOf(num);
if (result % 2 == 0 ){
return 0;
} else {
return 1;
}
}
}
向 WordCountDriver 类的代码中添加分区代码:
/**
* @author Jds
*/
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//输入数据的 Windows 路径,输出的 Windows 路径
args = new String[]{"C:\\Users\\Jds\\Desktop\\mapreduce\\Partition.txt",
"C:\\Users\\Jds\\Desktop\\mapreduce\\A2"};
//获取配置信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//反射三个类
job.setJarByClass(WordCountDriver.class);
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//Map 输出的 k,V 类型 Text, IntWritable 为 Reduce 的输入
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//Reduce 输出的 K,V 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//设置分区(新添加项)
job.setPartitionerClass(WordCountPartitioner.class);
job.setNumReduceTasks(2);
//数据的输入和输出指定目录
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//提交 job ---> waitForCompletion 包含 submit() 两个都是提交
job.waitForCompletion(true);
}
}
Combiner 合并:
将小文件进行合并,然后传给 Reduce ,减轻 Reduce 的工作量。
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//输入数据的 Windows 路径,输出的 Windows 路径
args = new String[]{"C:\\Users\\Jds\\Desktop\\mapreduce\\Text\\",
"C:\\Users\\Jds\\Desktop\\mapreduce\\A1"};
//获取配置信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//反射三个类
job.setJarByClass(WordCountDriver.class);
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//Map 输出的 k,V 类型 Text, IntWritable 为 Reduce 的输入
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//Reduce 输出的 K,V 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
/**
* Combiner
*/
job.setCombinerClass(WordCountReducer.class);
//设置分区
///job.setPartitionerClass(WordCountPartitioner.class);
//job.setNumReduceTasks(2);
//数据的输入和输出指定目录
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//提交 job ---> waitForCompletion 包含 submit() 两个都是提交
job.waitForCompletion(true);
}
}
InputFormat 切片机制 :
关于大量小文件的优化策略。
1、默认情况下 TextInputformat 对任务的切片机制是按文件规划切片,不管文件大小,都会是一个单独的切片,都会交给一个 maptask ,这样如果有大量小文件,就会产生大量的 maptask ,处理效率极其低下。
2、优化策略
1)最好的办法,在数据处理系统的最前端(预处理/采集),将小文件先合并成大文件,再上传到 HDFS 做后续分析。
2)补救措施:如果已经是大量小文件在 HDFS 中了,可以使用另一种 InputFormat 来做切片(CombineTextInputFormat),它的切片逻辑跟 TextFileInputFormat 不同:它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个 maptask 。
3)优先满足最小切片大小,不超过最大切片大小:
CombineTextInputFormat.setMaxInputSplitSize(job,3*1024*1024)
CombineTextInputFormat.setMinInputSplitSize(job,2*1024*1024)
3、具体实现步骤
// 如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class)
CombineTextInputFormat.setMaxInputSplitSize(job, 3*1024*1024);// 3m
CombineTextInputFormat.setMinInputSplitSize(job, 2*1024*1024);// 2m
注:在看 number of splits 时,和最大值(MaxSplitSize)有关、总体规律就是低于最大值是一片、高于最大值 1.5 倍 + ,则是两片;高于最大值 2 倍以上则向下取整,比如文件大小为 65MB ,切片最大值为 4MB ,那么切片为 16 个。总体来说,切片差值不超过 1 个,不影响整体性能。
规律:片数 = 文件的大小 / 最大值
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//输入数据的 Windows 路径,输出的 Windows 路径
args = new String[]{"C:\\Users\\Jds\\Desktop\\mapreduce\\Text\\",
"C:\\Users\\Jds\\Desktop\\mapreduce\\A1"};
//获取配置信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//反射三个类
job.setJarByClass(WordCountDriver.class);
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
//Map 输出的 k,V 类型 Text, IntWritable 为 Reduce 的输入
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
//Reduce 输出的 K,V 类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//Combiner
/// job.setCombinerClass(WordCountReducer.class);
/**
* 切片优化 如果不设置InputFormat,它默认用的是TextInputFormat.class
*/
job.setInputFormatClass(CombineTextInputFormat.class);
CombineTextInputFormat.setMaxInputSplitSize(job,3*1024*1024);
CombineTextInputFormat.setMinInputSplitSize(job,2*1024*1024);
//设置分区
///job.setPartitionerClass(WordCountPartitioner.class);
//job.setNumReduceTasks(2);
//数据的输入和输出指定目录
FileInputFormat.setInputPaths(job,new Path(args[0]));
FileOutputFormat.setOutputPath(job,new Path(args[1]));
//提交 job ---> waitForCompletion 包含 submit() 两个都是提交
job.waitForCompletion(true);
}
}