Hadoop之Mapreduce

本文详细介绍了Hadoop MapReduce的核心概念、编程模型、序列化问题、框架原理、数据压缩、YARN任务调度以及企业优化策略。内容涵盖了MapReduce的Mapper、Reducer、Driver阶段,数据序列化的原因和Hadoop自带的序列化机制,以及MapReduce的全排序、辅助排序、二次排序。此外,文章还讨论了Join操作、数据清洗、Combiner的使用,以及Hadoop数据压缩的重要性。通过对YARN的工作流程的解释,展示了MapReduce作业在Hadoop2.x时代的调度和执行过程,最后提出了MapReduce的优化方法,包括减少溢写和合并次数、数据倾斜处理等。
摘要由CSDN通过智能技术生成

MapReduce 定义

Mapreduce 是一个分布式运算程序的编程框架,是用户开发“基于 hadoop 的数据分析应用”的核心框架。Mapreduce 核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序,并发运行在一个 hadoop 集群上。

MapReduce 编程定式

Mapreduce用户编写程序的时候主要分为三个部分:Mapper,Reduce,Driver:

Mapper阶段

  1. 首先我们要继承mapper类为父类
  2. 确定输入的key,value的形式
  3. 将mapper逻辑写入map方法中
  4. 确定输出的 KV类型
  5. map方法对每一个key,value调用一次

Reduce阶段

  1. 继承Reducer为父类
  2. 输入的key,value形式同map阶段输出的一致
  3. 将逻辑写到reduce方法内
  4. reducetask对每一组key进行一次reduce方法

Driver阶段

整个程序需要一个 Drvier 来进行提交,提交的是一个描述了各种必要信息的 job 对象

举例:简单的wordcount实例

Mapper类:

package com.atguigu.mapreduce;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
   
Text k = new Text();
IntWritable v = new IntWritable(1);
@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) {
   
k.set(word);
context.write(k, v);
} } }

Reduce类:

package com.atguigu.mapreduce.wordcount;
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
   
@Override
protected void reduce(Text key, Iterable<IntWritable> value,
Context context) throws IOException, InterruptedException {
   
// 1 累加求和
int sum = 0;
for (IntWritable count : value) {
   
sum += count.get();
}
// 2 输出
context.write(key, new IntWritable(sum));
} }

编写驱动类:

package com.atguigu.mapreduce.wordcount;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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;
public class WordcountDriver {
   
public static void main(String[] args) throws IOException, ClassNotFoundException, 
InterruptedException {
   
// 1 获取配置信息
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
// 2 设置 jar 加载路径
job.setJarByClass(WordcountDriver.class);
// 3 设置 map 和 Reduce 类
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
// 4 设置 map 输出
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
// 5 设置 Reduce 输出
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 7 提交
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
} 
}

Hadoop的序列化问题

为什么要序列化?

因为我们要用hadoop进行分布式的计算那么就必定避免不了对象在网络中的传输那么就逃脱不了序列化这一过程。

什么是序列化?

序列化就是把内存中的对象,转换成字节序列(或其他数据传输协议)以便于存储(持久化)和网络传输。反序列化就是将收到字节序列(或其他数据传输协议)或者是硬盘的持久化数据,转换成内存中的对象。

为什么不用 Java 的序列化?

Java 的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(各种校验信息,header,继承体系等),不便于在网络中高效传输。所以,hadoop 自己开发了一套序列化机制(Writable),精简、高效。

常用数据序列化类型,java和hadoop对照

java类型 hadoop类型
boolean BooleanWritable
byte ByteWritable
int IntWritable
float FloatWritable
long LongWritable
double DoubleWritable
string Text
map MapWritable
array ArrayWritable

那么如果要序列化我们自己定义的bean对象呢?
要想在hadoop中自定义bean必须实现writable接口,同时还要注意因为hadoop在反序列化的时候要调用空参构造所以也要有个空参构造器

举例:

public FlowBean() {
   
super();
}
@Override
public void write(DataOutput out) throws IOException {
   
out.writeLong(upFlow);
out.writeLong(downFlow);
out.writeLong(sumFlow);
}
public void readFields(DataInput in) throws IOException {
   
upFlow = in.readLong();
downFlow = in.readLong();
sumFlow = in.readLong(); }

注意write和readFields函数中的写读顺序要一直,即当write中第一个写的是upFlow参数那么我们readFields的时候也要先读upFlow

Mapreduce框架原理回顾

在这里插入图片描述
解读:

  1. 提交任务之前先找到数据源目录

  2. 遍历文件夹中的每个数据,并获取其大小,然后进行切片计算
    a、切片大小计算公式computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M(其中minsize默认为1,maxsize默认为long的最大数,当我们改变minsize比默认的blocksize大时候则按照minsize为最后blocksize,而当maxsize比默认的数据块大小小时则最终的blocksize就去maxsize的值)
    b、没计算完一个切片都会看看文件剩余大小是不是大于blocksize的1.1倍,如果小于则将后边文件一起划分为一个切片
    c、根据切片信息生成切片规划文件,切片知识逻辑上的并非物理上的
    d、提交给yarn上,yarn的appMaster依据切片文件生成mapper。

  3. Mapper根据inputFormat的recordReader读取切片进入map逻辑中

  4. map逻辑走完会有一个collector收集结果然后写入环形缓冲区,环形环形缓冲区一般写索引一般写数据。

  5. 当数据达到环形缓冲区的80%后就开始溢写(之所以不等到满了是因为会阻塞):
    a、溢写前利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号partition 进行排序,然后按照 key 进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照 key 有序。
    b、如果用户设置了combiner则进行一次合并

  6. combine阶段:当所有数据处理完成后,maptask对所有临时文件进行合并,最终保证只会生成一个文件。在进行文件合并过程中,MapTask 以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合并 io.sort.factor(默认 100)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。

  7. reduce从多个mapper所生成的多个数据文件拷贝到缓存中如果缓存不够则溢出到本地磁盘。

  8. 因为数据是从多个地方而来reduce就进行了一次归并排序。

  9. 按照GroupingComparator规定的key将key相同的数据进入到同一个reduce中
    10.reduce统计完成后利用outputFormat的recordWriter写出处理结果。
    shuffle阶段就是从mapper输出开始到reduce输出为止。
    下面我们来逐一的介绍上面设计的环节对应的api设置

inputFormat

CombineInputFormat

在整个过程中我们的输入一定要避免是小文件==因为小文件的寻址时间大于传输时间,那我们如果启示就是很多小文件怎么办?
我们可以用另一个inputFormat,我们系统默认是使用TextInputformat,而我们只要将其设置为CombineTextInputFormar便可以在逻辑上将多个小文件划分到一块。
那么我们需要设置切片的最小值和最大值并且有一个原则为:优先满足最小,不超过最大切片大小举例:
0.5m+1m+0.3m+5m=2m + 4.8m=2m + 4m + 0.8m
设置切片大小的api:只需要在Driver中添加如下代码即可

job.setInputFormatClass(CombineTextInputFormat.class)
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m

除了上面提到的两个还有一些常见的inputFormat:

  • TextInputFormat(默认的):TextInputFormat 是默认的 InputFormat。每条记录是一行输入。键是 LongWritable 类型,存储该行在整个文件中的字节偏移量。值是这行的内容,不包括任何行终止符(换行符和回车符)。举例
    以下是一个示例,比如,一个分片包含了如下 4 条文本记录:
Rich learning form
Intelligent learning engine
Learning more convenient
From the real demand for more close to the enterprise

每条记录表示为以下键/值对:
(0,Rich learning form)
(19,Intelligent learning engine)
(47,Learning more convenient)
(72,From the real demand for more close to the enterprise)

  1. KeyValueTextInputFormat:每一行均为一条记录,被分隔符分割为 key,value。可以通过在驱动类中设置conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, " ");来设定分隔符。默认分隔符是 tab(\t)。
  2. NLineInputFormat:如果使用 NlineInputFormat,代表每个 map 进程处理的 InputSplit 不再按 block 块去划分,而是按 NlineInputFormat 指定的行数 N 来划分。即输入文件的总行数/N=切片数,如果不整除,切片数=商+1。

自定义inputformat

1、首先我们要继承Fileinputformat
2、改写 RecordReader,实现一次读取一个完整文件封装为 KV
3、在输出时使用 SequenceFileOutPutFormat 输出合并文件。
举例:
无论 hdfs 还是 mapreduce,对于小文件都有损效率,实践中,又难免面临处理大量小文件的场景,此时,就需要有相应解决方案。将多个小文件合并成一个文件 SequenceFile,SequenceFile 里面存储着多个文件,存储的形式为文件路径+名称为 key,文件内容为 value。

自定义FileInputFormat

package com.atguigu.mapreduce.inputformat;
import java.io.IOException;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
// 定义类继承 FileInputFormat
public class WholeFileInputformat extends FileInputFormat<NullWritable, BytesWritable>{
   
@Override
protected boolean isSplitable(JobContext context, Path filename) {
   
return false;
}
@Override
public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit split, 
TaskAttemptContext context)
throws IOException, InterruptedException {
   
WholeRecordReader recordReader = new WholeRecordReader();
recordReader.initialize(split, context);
return recordReader;
} }

自定义RecordReader

package com.atguigu.mapreduce.inputformat;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
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;
public class WholeRecordReader extends RecordReader<NullWritable, BytesWritable>{
   
private Configuration configuration;
private FileSplit split;
private boolean processed = false;
private BytesWritable value = new BytesWritable();
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, 
InterruptedException {
   
this.split = (FileSplit)split;
configuration = context.getConfiguration();
}
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
   
if (!processed) {
   
// 1 定义缓存区
byte[] contents = new byte[(int)split.getLength()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值