Hadoop-MapReduce原理过程

MapReduce的定义 

Hadoop 中的 MapReduce 是一个使用简单的软件框架,基于它写出来的应用程序能够运行在由上千个商用机器组成的大型集群上,并以一种可靠容错式并行处理TB级别的数据集

mapreduce的优点

1、MapReduce 易于编程 。它简单的实现一些接口,就可以完成一个分布式程序,这个分布式程序可以分布到大量廉价的 PC 机器运行。也就是说你写一个分布式程序,跟写一个简单的串行程序是一模一样的。就是因为这个特点使得 MapReduce 编程变得非常流行。

2、良好的 扩展性 。当你的计算资源不能得到满足的时候,你可以通过简单的增加机器来扩展它的计算能力。

3、 高容错性 。MapReduce 设计的初衷就是使程序能够部署在廉价的 PC 机器上,这就要求它具有很高的容错性。比如其中一台机器挂了,它可以把上面的计算任务转移到另外一个节点上面上运行,不至于这个任务运行失败,而且这个过程不需要人工参与,而完全是由Hadoop 内部完成的。

4、适合 PB 级以上海量数据的 离线处理 。这里加红字体离线处理,说明它适合离线处理而不适合在线处理。比如像毫秒级别的返回一个结果,MapReduce 很难做到。


mapreduce的不能实现的

1 、实时计算。MapReduce 无法像 Mysql 一样,在毫秒或者秒级内返回结果。

2、流式计算。流式计算的输入数据时动态的,而 MapReduce 的输入数据集是静态的,不能动态变化。这是因为 MapReduce 自身的设计特点决定了数据源必须是静态的。    

3、DAG(有向图)计算。多个应用程序存在依赖关系,后一个应用程序的输入为前一个的输出。在这种情况下,MapReduce 并不是不能做,而是使用后,每个MapReduce 作业的输出结果都会写入到磁盘,会造成大量的磁盘IO,导致性能非常的低下。

扫描二维码关注公众号,回复: 5839474 查看本文章

MapReduce运行过程
 

https://img-blog.csdn.net/20171228164304301?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTY2MzM0MDU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

如上图所示是MR的运行详细过程:图中用红色粗线描述的组件都是可以重写的。

maptask阶段

1、首先mapTask读文件是通过InputFormat(内部是调RecordReader()–>read())来一次读一行,返回K,V值。(默认是TextInputFormat,还可以输入其他的类型DBInputFormat读取数据库数据,squeceinputFormat读取squece文件)。程序会根据InputFormat将输入文件分割成splits,每个split会作为一个map task的输入。

2、mapper(map(k,v))–>context.write()

shuffle阶段

1、输出数据到OutputCollector收集器(不会输出一组就传到下一步进行处理,而是需要一个收集的过程,减少IO)

2、将收集到的数据写到环形缓冲区(数据结构其实就是个字节数组byte[]),通过Spiller来将溢出的数据溢出到文件中去(在这里会通过hashPartitioner执行分区、通过key.comPareTo来实现排序(分为系统默认的快排和外部排序)即实现了shuffle的核心机制:分区和排序)。

3、将多个溢出文件进行Merge(采用归并排序),合并成一个大文件(一个大文件对应一个maptask)

4、将文件下载到ReduceTask的本地磁盘工作目录–>将多个MapTask的输出大文件再进行归并排序(也可以说是ReduceTask去各个mapTask对应的分区去取对应的数据)。

reducetask阶段

1、Reducer(reduce(k,v))–>context.write(k,v)–>OutputFormat(RecordWriter.write(k,v))即reduceTask阶段。

2、将数据写到part-r-00000这里

MapReduce编程模式

1、输入数据接口:InputFormat —> FileInputFormat(文件类型数据读取的通用抽象类) DBInputFormat (数据库数据读取的通用抽象类,默认使用的实现类是 :TextInputFormat。job.setInputFormatClass(TextInputFormat.class)TextInputFormat的功能逻辑是:一次读一行文本,然后将该行的起始偏移量作为key,行内容作为value返回

2、逻辑处理接口: Mapper,完全需要用户自己去实现其中 map() setup() clean() 

3、map输出的结果在shuffle阶段会被partition以及sort,此处有两个接口可自定义:Partitioner有默认实现 HashPartitioner,逻辑是 根据key和numReduces来返回一个分区号; key.hashCode()&Integer.MAXVALUE % numReduces,通常情况下,用默认的这HashPartitioner就可以,如果业务上有特别的需求,可以自定义分区实现Partitioner接口,重写getpartition()方法。当我们用自定义的对象作为key来输出时,就必须要实现WritableComparable接口,override其中的compareTo()方法。

4、reduce端的数据分组比较接口 : Groupingcomparator,reduceTask拿到输入数据(一个partition的所有数据)后,首先需要对数据进行分组,其分组的默认原则是key相同,然后对每一组kv数据调用一次reduce()方法,并且将这一组kv中的第一个kv的key作为参数传给reduce的key,将这一组数据的value的迭代器传给reduce()的values参数

5、逻辑处理接口:Reducer
完全需要用户自己去实现其中 reduce() setup() clean()

6、输出数据接口: OutputFormat —> 有一系列子类 FileOutputformat DBoutputFormat …..
默认实现类是TextOutputFormat,功能逻辑是: 将每一个KV对向目标文本文件中输出为一行
 

MapReduce注意事项

环形缓存区:(数据从outputCollector中传入环形缓存区,直到达到80%的缓存时,缓存才会启用清理机制,将已经溢出的数据溢出到文件中去(通过spiller来将数据溢出到文件中去))会溢出多次,每次溢出都会对数据进行分区排序,形成多个分区排序后的数据,最终进行合并。
combiner的作用:对spiller阶段的溢出数据进行一个reduce处理,直接让相同k的value值相加,减少数据量以及传输过程中的开销,大大提高效率。(根据业务需求使用,并不是每个业务都要用。可自定义一个Combiner类,内部逻辑和Reduce类似)

shuffle:洗牌、发牌——(核心机制:数据分区,排序,缓存)具体来说:就是将maptask输出的处理结果数据,分发给reducetask,并在分发的过程中,对数据按key进行了分区和排序;

数据倾斜:指的是任务在shuffle阶段时会进行一个分区操作(默认的是hashcode取模),如果有大部分数据被分到一个ReduceTask端进行处理,一小部分任务被分到其他的ReduceTask端进行处理,就会造成其他ReduceTask处理完成后,仍有一个ReduceTask还在处理数据。最终造成整个工程延迟的情况。(为了解决这个问题,引入了Partition)

大量小文件优化:默认的情况下TextInputFormat是按照文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个maptask。如果有大量的小文件,就会产生大量的maptask,处理效率低下。最好先将小文件合成大文件后上传的hdfs。如果已经存在hdfs中了,可以使用另种inputFormat来做切片(ConbineFileInputFormat的子类ConbineTextInputFormat),它的切片逻辑和TextInputFormat不同,它可以将多个小文件规划到一个切片中,这样多个小文件就可以交给一个maptask去处理。

wordcount的代码实现

需要jar包

Hadoop-2.4.1\share\hadoop\hdfs\hadoop-hdfs-2.4.1.jar
hadoop-2.4.1\share\hadoop\hdfs\lib\所有jar包

hadoop-2.4.1\share\hadoop\common\hadoop-common-2.4.1.jar
hadoop-2.4.1\share\hadoop\common\lib\所有jar包

hadoop-2.4.1\share\hadoop\mapreduce\除hadoop-mapreduce-examples-2.4.1.jar之外的jar包
hadoop-2.4.1\share\hadoop\mapreduce\lib\所有jar包

mapper类实现

/*
 * KEYIN:输入kv数据对中key的数据类型
 * VALUEIN:输入kv数据对中value的数据类型
 * KEYOUT:输出kv数据对中key的数据类型
 * VALUEOUT:输出kv数据对中value的数据类型
 */
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable>{
	
	/*
	 * map方法是提供给map task进程来调用的,map task进程是每读取一行文本来调用一次我们自定义的map方法
	 * map task在调用map方法时,传递的参数:
	 * 		一行的起始偏移量LongWritable作为key
	 * 		一行的文本内容Text作为value
	 */
	@Override
	protected void map(LongWritable key, Text value,Context context) throws IOException, InterruptedException {
		//拿到一行文本内容,转换成String 类型
		String line = value.toString();
		//将这行文本切分成单词
		String[] words=line.split(" ");
		
		//输出<单词,1>
		for(String word:words){
			context.write(new Text(word), new IntWritable(1));
		}
	}
}

reducer类实现

/*
 * KEYIN:对应mapper阶段输出的key类型
 * VALUEIN:对应mapper阶段输出的value类型
 * KEYOUT:reduce处理完之后输出的结果kv对中key的类型
 * VALUEOUT:reduce处理完之后输出的结果kv对中value的类型
 */
public class WordCountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
	@Override
	/*
	 * reduce方法提供给reduce task进程来调用
	 * 
	 * reduce task会将shuffle阶段分发过来的大量kv数据对进行聚合,聚合的机制是相同key的kv对聚合为一组
	 * 然后reduce task对每一组聚合kv调用一次我们自定义的reduce方法
	 * 比如:<hello,1><hello,1><hello,1><tom,1><tom,1><tom,1>
	 *  hello组会调用一次reduce方法进行处理,tom组也会调用一次reduce方法进行处理
	 *  调用时传递的参数:
	 *  		key:一组kv中的key
	 *  		values:一组kv中所有value的迭代器
	 */
	protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
		//定义一个计数器
		int count = 0;
		//通过value这个迭代器,遍历这一组kv中所有的value,进行累加
		for(IntWritable value:values){
			count+=value.get();
		}
		
		//输出这个单词的统计结果
		context.write(key, new IntWritable(count));
	}
}

job提交客户端实现:

public class WordCountJobSubmitter {
	
	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		Configuration conf = new Configuration();
		Job wordCountJob = Job.getInstance(conf);
		
		//重要:指定本job所在的jar包
		wordCountJob.setJarByClass(WordCountJobSubmitter.class);

		//指定自定义的分区器
		//wordCountJob.setPartitionerClass(ProvincePartitioner.class);
      
		//指定reduceTask的个数
		//wordCountJob.setNumReduceTask(5)
		
		//设置wordCountJob所用的mapper逻辑类为哪个类
		wordCountJob.setMapperClass(WordCountMapper.class);
		//设置wordCountJob所用的reducer逻辑类为哪个类
		wordCountJob.setReducerClass(WordCountReducer.class);
		
		//设置map阶段输出的kv数据类型
		wordCountJob.setMapOutputKeyClass(Text.class);
		wordCountJob.setMapOutputValueClass(IntWritable.class);
		
		//设置最终输出的kv数据类型
		wordCountJob.setOutputKeyClass(Text.class);
		wordCountJob.setOutputValueClass(IntWritable.class);
        
		//指定需要Conbiner,以及使用哪个类作为Conbine的逻辑
		//wordCountJob.setCombinerClass(WordCountReducer.class);
        
		//如果不设置InputFormat,它默认是TextInputFormat.class
		//wordCountJob.setInputFormatClass(CombineTextInputFormat.class);
		//CombineTextInputFormat.setMaxInputSplitSize(wordCountJob,4194304);
		//CombineTextInputFormat.setMinInputSplitSize(wordCountJob,2097152);

		//设置要处理的文本数据所存放的路径
		FileInputFormat.setInputPaths(wordCountJob, new Path(args[0]));
		//设置输出结果所存放的路径
		FileOutputFormat.setOutputPath(wordCountJob, new Path(args[1]));
		
		//提交job给hadoop集群
		wordCountJob.waitForCompletion(true);
	}
}

原文:
https://blog.csdn.net/qq_16633405/article/details/78924165
https://blog.csdn.net/litianxiang_kaola/article/details/71154302

猜你喜欢

转载自blog.csdn.net/weixin_42782897/article/details/89185178