MapReduce
MapReduce概述
MapReduce是一个分布式运算程序,核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在Hadoop集群上。特点是易于编程,用户只用关心业务逻辑,有良好的拓展性,可以动态增加服务器的数量,高容错,可以在任务挂掉的时候将任务转移给其他节点;适合海量数据计算。缺点是不适合实时计算,不适合流式数据,不擅长DAG有向无环图计算
MapReduce核心思想
MapReduce分为两个阶段,Map阶段和Reduce阶段,Map阶段的的并发MapTask完全并发运行互不干扰,Reduce阶段的并发ReduceTask互不相干,但是他们的数据依赖于Map阶段的输出,MapReduce编程模型只能包含一个Map阶段和一个Reduce阶段,如果用户的业务逻辑比较复杂,只能多个MapReduce程序串行运行
MapReduce进程
- MrAppMaster:负责整个程序的过程调度及状态协调
- MapTask:负责Map阶段的整个数据处理流程
- ReduceTask:负责Reduce阶段的整个数据处理流程
常用类型
Mapper阶段编程规范
- 自定义的mapper要继承自己的父类
- Mapper的输入和输出要求为k-v 键值对的形式
- 业务逻辑写在map方法中,并且对每一个k-v调用一次
Reducer阶段编程规范
- 自定义的reducer要继承自己的父类
- 输入类型对应mapper的输出
- 业务逻辑写在reduce方法中
- reduce对每一组相同的key调用一次reduce方法
Driver阶段的步骤
- 获取配置信息,获取job对象实例
- 指定本程序的jar包所在的本地路径
- 关联mapper类和reducer类
- 指定mapper输出数据的kv类型
- 指定最终输出的数据kv类型
- 指定job的输入原始文件所在目录
- 指定job的输出结果所在目录
- 提交作业
WorldCount案例
编写Mapper
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text outK = new Text();
private IntWritable outV = new IntWritable(1); //map阶段不进行聚合
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行
String line = value.toString();
// 2 切割(取决于原始数据的中间分隔符)
String[] words = line.split(" ");
// 3 循环写出
for (String word : words) {
// 封装outk
outK.set(word);
// 写出
context.write(outK, outV);
}
}
}
编写Reducer
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
private IntWritable outV = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
// 将values进行累加
for (IntWritable value : values) {
sum += value.get();
}
outV.set(sum);
// 写出
context.write(key,outV);
}
}
编写Driver
public class WordCountDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1 获取job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 设置jar包路径
job.setJarByClass(WordCountDriver.class);
// 3 关联mapper和reducer
job.setMapperClass(WordCountMapper.class);
job.setReducerClass(WordCountReducer.class);
// 4 设置map输出的kv类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置最终输出的kV类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入路径和输出路径
FileInputFormat.setInputPaths(job, new Path("D:\\input\\inputword"));
FileOutputFormat.setOutputPath(job, new Path("D:\\hadoop\\output888"));
// 7 提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
}
}
实现Bean对象的序列化
- 实现Writable接口
- 必须要有空参构造方法,因为反序列化需要反射调用空参构造函数
- 重写序列化方法
- 重写反序列化方法
- 序列化和反序列化的顺序要求一致
- 想要把结果展现在文件中,需要重写toString方法
- 如果将自定义的Bean放到key中传输,则需要实现Comparable接口,因为MapReduce中的shuffle过程需要对key才能进行排序
原理
InputFormat数据输入
MapTask的并行度决定Map阶段的任务处理并发度,进而影响整个Job的处理速度。
MapTask的并行度决定机制
-
数据块:HDFS的存储单位
-
数据切片:逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。数据切片是MapReduce程序计算输入数据的单位,一个切片会对应启动一个MapTask
切片大小和数据块大小一致时效率比较高,默认情况一样大
FileInputFormat源码解析
- 程序找到数据存储目录
- 开始遍历处理目录下的每个文件
- 遍历每个文件时,首先获取文件大小,然后计算切片大小,受minSize、maxSize、blockSize影响,默认情况为blockSize,每次切片时都要查看剩余部分是否大于块的1.1倍,否则将其切为1片。切片公式:
Math.max(minSize,Math.min(maxSize,blockSize))
,默认情况minSize为1,maxSize为Long的最大值 - 提交切片规划到Yarn文件上,这样MrAppMaster就可以根据切片规划文件计算开启MapTask
小文件处理
小文件过多会产生大量的MapTask,处理效率极其低下,因此可以用CombineTextInputFormat处理小文件过多的场景,这样多个小文件可以合并成一个切片进行处理。可以通过设置MaxInputSplitSize来调整虚拟存储切片的大小,从而控制小文件合并。
MapReduce工作流程
Shuffle机制
map之后,reduce之前。
- 首先给数据标记分区,然后进入环形缓冲区,默认大小为100M,到达80%的时候开始反向溢写,溢写之前要对数据进行快速无序(对索引按照字典顺序排),溢写会有多次,最后会用归并排序对这些文件以分区进行排序,接下来对数据进行压缩,然后写到磁盘上,等待reduce来拉取数据
- reduce来拉取数据,会尝试将数据放到内存,内存不够会放到磁盘,然后对数据进行归并排序、分组、reduce方法。
自定义Partitioner步骤
- 自定义类继承Partitioner,重写getPartition方法,分区号必须从0开始
- job驱动中设置自定义的partitioner:
job.setPartitionerClass(CustomPartitioner.class)
- 设置reduceTask,如果不大于1则不会走自定义的Partitioner:
job.setNumReduceTasks(2);
,如果小于partition的数量会爆异常,如果大于partition的数量会产生空文件
自定义排序
当缓冲队列到达百分之80时,会进行快速排序,排的是key。而当自定义类型作为key时,就需要使得自定义类型实现WriteComparable接口。
Combiner
Combiner是mr之外的一种组件,是可选的。父类是Reducer,区别在于Combiner是每一个Maptask所在的节点运行,Reducer是接收全局所有mapper的输出结果。Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减少网络传输量。实际场景中不需要自己定义,使用自己的Reducer即可。
OutputFormat源码解析
Reduce之后默认使用textOutputFormat,按行写出。可以自定义OutputFormat来实现文件的写出,比如写到mysql、写到es、写到log等。
public class LogOutputStream extends FileOutputStream<Text,NullWritable> {
private FSDataOutputStream out1;
private FSDataOutputStream out2;
public RecordWriter<Text,NullWritable> getRecordWriter(TaskAttemptContext job) throws IOException,InterruptedException{
LogRecordWriter lrw = new LogRecordWriter(job);
return lrw;
}
}
public class LogRecordWriter extends RecordWriter<Text,NullWritable> {
public LogRecordWriter(TaskAttemptContext job){
FileSystem fs=FileSystem.get(job.getConfiguration());
out1 = fs.create(new Path("输出路径1"));
out2 = fs.create(new Path("输出路径2"));
}
public void write(Text key, NullWritable value) throws IOException,InterruptedException {
String log=key.toString();
if(log.contains("xxxx")){
out1.writeBytes(log);
}else{
out2.writeBytes(log);
}
}
public void close(TaskAttemptContext context) throws IOException,InterruptedException {
IOUtils.closeStream(out1);
IOUtils.closeStream(out2);
}
}
MapTask工作机制
Read阶段
客户端对文件进行切片划分,然后提交给yarn,包括Job.split、wc.jar、Job.xml,接下来yarn开启MrAppMaster。MrAppMaster计算出MapTask数量,每个MapTask由inputFormat去读去数据,默认key为偏移量,value为一行数据。
1.Map阶段
由用户自定义的map来实现
2.Collect阶段
环形缓冲区内部进行的分区和排序
3.溢写阶段
环形缓冲区产生大量的溢写文件
4.Merge阶段
对溢写阶段的文件进行归并合并
ReduceTask工作机制
1.Copy阶段
拉取自己指定分区的数据
2.Sort阶段
对拉取的数据进行归并排序
3.Reduce阶段
reduce方法的操作
ReduceTask并行度
Map Join
利用MapReduce合并两张表的话可以讲共同字段作为key,然后在reduce端进行合并,但是产生的问题是如果数据量特别大,对于reduce端的压力会很大。因此可以用Map join的方式。
思想:适用于一张小表和一张大表的情况,将小表放到内存中,然后大表直接对应。
方案:采用DistributedCache,将小表以缓存数据加载,在mapper的setup方法中将文件读取到缓存集合中。
driver加载缓存文件:job.addCacheFile("路径")
setup中获取缓存文件:context.getCacheFiles();
压缩
压缩算法介绍
压缩性能比较
压缩算法选择:
Map输入端:数据量小则考虑压缩和解压缩速率,比如LZO和snappy;数据量大则考虑是否能切片选Bzip2和LZO
Map->Reduce:减少网络IO,重点考虑速率LZO和snappy
reduce输出端:如果数据永久保存,考虑压缩率,则选Bzip2和Gzip