Hadoop——MapReduce(3)

MapReduce:自己处理业务相关代码 + 自身的默认代码

1.MapReduce优缺点

优点:

  • 1、易于编程,用户只关心业务逻辑;实现框架的接口
  • 2、良好的扩展性,可以动态增加服务器,解决计算资源不够问题
  • 3、高容错性,任何一台机器挂掉,可以将任务转移到其他节点
  • 4、适合海量数据计算(TP/PB),几千台服务器共同计算

缺点:

  • 1、不擅长实时计算,Mysql
  • 2、不擅长流式计算,Sparkstreaming flink
  • 3、不擅长DAG有向无环图计算,spark

2.MapReduce进程

一个完整的MapReduce在分布式运行时有三类实例进程:

  • MrAppMaster:负责整个程序的过程调度及状态协调
  • MapTask:负责Map阶段的整个数据处理流程
  • ReduceTask:负责Reduce阶段的整个数据处理流程

3.序列化

  • 序列化:将内存中的对象——>字节序列(或其他数据传输协议),以便于存储到磁盘(持久化)和网络传输
  • 反序列化:将收到的字节序列(或其他数据传输协议)->内存中的对象
  • 为什么要序列化:序列化可以存储“活的”对象,将“活的”对象发送到远程计算机
  • 为什么不用Java的序列化:Java的序列化是一个重量级序列化框架(Serializable),一个对象被序列化后,会附带很多额外的信息(如:校验信息,Header,继承体系),不便于在网络中高效传输。Hadoop因此开发了一套自己的序列化机制(Writable)
  • Hadoop序列化的特点:
    • (1)紧凑:高效使用存储空间
    • (2)快速:读写数据的额外开销小
    • (3)互操作:支持多语言的交互

4 InputFormat数据输入

4.1 切片与MapTask并行度决定机制
  • 问题:MapTask的并行度决定Map阶段的任务处理并发度,进而影响到整个Job的处理速度
  • MapTask并行度决定机制
    • 数数据块:Block是HDFS物理上把数据分成一块一块,数据块是HDFS存储数据单位
    • 数据切片:数据切片只是在逻辑上对输入进行分片,并不会在磁盘上将其切分成片进行存储。数据切片是MapReduce程序计算输入数据的单位,一个切片会对应启动一个MapTask

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aVHmGgx6-1642258925004)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220114223819869.png)]

4.2 Job提交流程源码详解
waitForCompletion()

submit();

// 1建立连接
	connect();	
		// 1)创建提交Job的代理
		new Cluster(getConfiguration());
			// (1)判断是本地运行环境还是yarn集群运行环境
			initialize(jobTrackAddr, conf); 

// 2 提交job
submitter.submitJobInternal(Job.this, cluster)

	// 1)创建给集群提交数据的Stag路径
	Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);

	// 2)获取jobid ,并创建Job路径
	JobID jobId = submitClient.getNewJobID();

	// 3)拷贝jar包到集群
copyAndConfigureFiles(job, submitJobDir);	
	rUploader.uploadFiles(job, jobSubmitDir);

	// 4)计算切片,生成切片规划文件
writeSplits(job, submitJobDir);
		maps = writeNewSplits(job, jobSubmitDir);
		input.getSplits(job);

	// 5)向Stag路径写XML配置文件
writeConf(conf, submitJobFile);
	conf.writeXml(out);
	// 6)提交Job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jmAJtiLq-1642258925006)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220114224434552.png)]

4.3 FileInputFormat 切片机制

切片机制:

  • 1.简单地按照文件的内容长度进行切片
  • 2.切片大小,默认等于Block大小
  • 3.切片时不考虑数据集整体,而是逐个针对每一个文件单独切片

切片源码解析:

(1)源码中计算切片大小的公式
Math.max(minSize,Math.min(maxSize,blockSize))
mapreduce.input.fileinputformat.split.minsize=1	//默认值为1
mapreduce.input.fileinputformat.split.maxsize=Long.MAXValue	//默认值Long.MAXValue

(2)切片大小设置
maxsize(切片最大值):参数如果调得比blockSize小,则会让切片变小,此时切片大小=maxsize
minsize(切片最小值):参数调的比blockSize大,让切片变大,此时切片大小=minSize

(3)获取切片信息API
String name = inputSplit.getPath().getName();	//获取切片的文件名称
FileSplit inputSplit = (FileSplit) contxt.getInputSplit();	//根据文件类型获取切片信息
4.4 FileInputFormat

FileInputFormat常见的接口实现类:

TextInputFormat、KeyValueTextInputFormat、NLineInputFormat、CombineTextInputFormat和自定义InputFormat等
  • TextInputFormat:FileInputFormat默认实现类,按行读取每条记录。
    • 键:存储该行在整个文件中的起始字节偏移量,LongWritable类型
    • 值:改行内容,不包括任何行终止符(换行符和回车符),Text类型
4.5 CombineTextInputFormat切片机制

Combine使用案例
​ 说明:框架默认的TextInputFormat切片机制是对任务按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个MapTast,这样如果有大量小文件,就会产生大量的MapTask,处理效率及其低下。

(1)应用场景
CombineTextInputFormat用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件交给一个MapTask处理

(2)虚拟存储切片最大值设置
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
注意:虚拟存储切片最大值设置最好根据实际的小文件大小情况来设置具体的值

(3)切片机制,生成切片过程包括:虚拟存储过程和切片过程两部分
	虚拟存储过程:将输入目录下所有文件大小,依次和设置的setMaxInputSplitSize值比较,如果不大于设置的最大	 值,逻辑上划分一个块。如果输入文件大于设置的最大值且大于两倍,那么以最大值切割一块;当剩余数据大小超过设置的最大值且不大于最大值2倍,此时将文件均分成2个虚拟存储块(防止出现太小切片)。
	例如setMaxInputSplitSize值为4M,输入文件大小为8.02M,则先逻辑上分成一个4M。剩余的大小为4.02M,如果按照4M逻辑划分,就会出现0.02M的小的虚拟存储文件,所以将剩余的4.02M文件切分成(2.01M和2.01M)两个文件
	
	切片过程:
(a)判断虚拟存储的文件大小是否大于setMaxInputSplitSize值,大于等于则单独形成一个切片。
(b)如果不大于则跟下一个虚拟存储文件进行合并,共同形成一个切片。
(c)测试举例:有4个小文件大小分别为1.7M、5.1M、3.4M以及6.8M这四个小文件,则虚拟存储之后形成6个文件块,大小分别为:1.7M,(2.55M、2.55M),3.4M以及(3.4M、3.4M)
最终会形成3个切片,大小分别为:(1.7+2.55M,(2.55+3.4M,(3.4+3.4M

5.MapReduce工作流程

在这里插入图片描述
在这里插入图片描述

6.Shffle机制

6.1Shuffle机制大纲流程

Map方法之后,Reduce方法之前的数据处理过程称之为Shuffle

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-im2fem0G-1642258925007)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220115210004789.png)]

(1)MapTask收集我们的map方法输出kv对,放到内存缓冲区中
(2)从内存缓冲区不断溢出本地磁盘文件,可能会溢出多个文件
(3)多个溢出文件会被合并成大的溢出文件
(4)在溢出过程及合并的过程中,都要调用Partition进行分区和针对key进行排序
(5)ReduceTask根据自己的分区号,去各个MapTask机器上取相应的结果分区数据
(6)ReduceTask会抓取到同一个分区的来自不同MapTask的结果文件,ReduceTask会将这些文件再进行合并(归并排序)
(7)合并成大文件后。Shuffle的过程也就结束了,后面进入ReduceTask的逻辑运算过程(从文件中取出一个一个的键值对Group,调用用户自定义的reduce()方法)

注意:(1)Shuffle中的缓冲区大小会影响到MapReduce程序的执行效率,原则上说,缓冲区越大,磁盘io的次数越少,执行速度就越快。
(2)缓冲区的大小可以通过参数调整,参数:mapreduce.task.io.sort.mb默认100M
6.2 Partition分区

Partition分区的使用案例

1.问题引出:
要求将统计结果按照条件输出到不同文件中(分区)

2.默认Partitioner分区
public class HashPartitioner<K, V> extends Partitioner<K, V> {
	public int getPartition(K key, V value, int numReduceTasks) {
		return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
		}
}
默认分区是根据key的hashCode对ReduceTask个数取模得到的,用户没法控制哪个key存储到哪个分区

3.自定义Partition步骤
	(1)自定义类继承Partitioner,重写getPartition方法
	public class CustomPartitioner extends Partitioner<Text, FlowBean> {
		@Override
		public int getPartition(Text key, FlowBean value, int numPartitions) {
		// 控制分区代码逻辑
		… …
			return partition;
		}
	}
	(2)Job驱动中,设置自定义Partitioner
		job.setPartitionerClass(CustomPartitioner.class)
	(3)自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask
		job.setNumReduceTasks(5); //输出到五个文件,分区设置为5

4.分区总结
	(1)如果ReduceTask的数量 > getPartition的结果数,则会多产出几个空的输出文件part-r-000xx;
	(2)如果1 < ReduceTask的数量 < getPartition的结果数,则有一部分分区数据无处安放,会Exception;
	(3)如果ReduceTask的数量=1,则不管MapTask端出数多少个分区文件,最终结果都交给这一个ReduceTask,最终也就只会产出一个结果文件part-r-00000;
	(4)分区号必须从零开始,逐一累加

5.案例分析
例如:假设自定义分区数为5,则
	(1)job.setNumReduceTasks(1);	会正常运行,只不过会产生一个输出文件
	(2)job.setNumReduceTasks(2);	会报错
	(3)job.setNumReduceTasks(6);	大于5,程序会正常运行,会产生空文件
6.3 WritableComparable排序

WritableComparable排序的使用案例

排序是MapReduce框架中最重要的操作之一
	MapTask和ReduceTask均会对数据按照key进行排序。该操作属于Hadoop的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。(默认排序是按照字典顺序排序,且实现该排序的方法是快速排序)
	对于MapTask,它会将处理的结果暂时存放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序
	对于ReduceTask,它每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写到磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序
	
排序分类:
	(1)部分排序:MapReduce根据输入记录的键对数据集排序,保证输出的每个文件内部有序
	(2)全排序:最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask,但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了MapReduce所提供的并行架构。
	(3)辅助排序(GroupingComparator分组):在Reduce端对key进行分组,在接收的key为bean对象时,想让一个或几个字段相同的key进入到同一个reduce方法时,可以采用分组排序
	(4)二次排序:自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序
6.4 Combiner合并

Combiner合并的使用案例

1.Combiner是MR程序中MapperReducer之外的一种组件
2.Combiner组件的父类是Reducer
3.Combiner和Reducer的区别在于运行的位置
	Combiner是在每一个MapTask所在的节点运行
	Reducer是在接收全局所有Mapper的输出结果
4.Combiner的意义就是对每一个MapTask的输出进行局部汇总,以减小网络传输量
5.Combiner能够应用的前提是不能影响最终的业务逻辑,而且Combiner的输出kv与Reducer的输入kv类型要对应
	使用场景(求和√,求均值×)
求均值不行的例子:	Mapper			Reducer
				3 5 7 ->5			3 5 7 2 6 -> 23/5 正确的
				2 6 ->4				4 5 -> 9/2 使用场景错误导致不同的输出
6.自定义Combiner实现步骤
	(a)自定义一个Combiner继承Reducer,重写Reduce方法
	public class WordCountCombiner extends Reducer<Text, IntWritable, Text,IntWritable> {
 		private IntWritable outV = new IntWritable();
 		@Override
 		protected void reduce(Text key, Iterable<IntWritable> values, Contextcontext) throws IOException, InterruptedException {
 			int sum = 0;
 			for (IntWritable value : values) {
			 	sum += value.get();
 			}

			 outV.set(sum);
 			context.write(key,outV);
 		}
	}
	(b)Job驱动类中设置
     job.setCombinerClass(WordCountCombiner.class);

7.OutputFormat数据输出

OutputFormat案例实操

OutputFormat是MapReduce输出的基类,所有实现MapReduce输出都实现了OutputFormat接口,默认格式TextOutputFormat

FileOutputFormat(含TextOutputFormat,SequenceFileOutputFormat,MapFileOutputFormat),FilterOutputFormat,DBOutputFormat

自定义OutPutFormat
1.应用场景:输出数据到MySQL/HBase/Elasticsearch等存储框架中
2.自定义OutputFormat步骤
	a.自定义一个类继承FileOutputFormat
	b.改写RecordWriter,具体改写输出数据的方法write()

8.MapTask工作机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8cMEd9S8-1642258925008)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220115220711161.png)]

(1)Read阶段:MapTask通过InputFormat获得的RecordReader,从输入InputSplit中解析处一个个key/value
(2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value
(3)Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它将会生成的key/value分区(调用Partition),并写入一个环形内存缓冲区中
(4)Spill(溢写)阶段:当环形缓冲区满后,MapReducer会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作

	步骤1:利用快速排序算法对缓冲区的数据进行排序,排序方式是,先按照分区编号Partition进行排序,然后按照key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序
	步骤2:安装分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示当前溢写次数)中。如果用户设置Combiner,则写入文件之前,对每个分区中数据进行一次聚集操作。
	步骤3:将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小后压缩后数据大小。如果当前内存索引大小1MB,则将内存索引写到文件output/spillN.out.index中

(5)Merge阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
	当所有数据处理完后,MapTask会将所有临时文件合并成一个大文件,并保持到文件output/file.out中,同时生成相应的索引文件output/file.out.index
	在进行文件合并过程中,MapTask以分区为单位进行合并,对于某个分区,它将采用多轮递归合并的方式。每轮合并mapreduce.task.io.facyor(默认10)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。
	让MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销

9.ReduceTask工作机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hVZFkrGB-1642258925008)(C:\Users\Admin\AppData\Roaming\Typora\typora-user-images\image-20220115223649000.png)]

(1)Copy阶段:ReduceTask从各个MapTask上远程拷贝一片数据,并针对某一片数据,如果其大小超过一定阈值,则写到磁盘上,否则直接放到内存中。
(2)Sort阶段:在远程拷贝数据的同时,ReduceTask启动了两个后台线程对内存和磁盘上的文件进行合并,以防止内存使用过多或磁盘上文件过多。按照MapReduce语义,用户编写reduce()函数输入数据时按key进行聚集的一组数据。为了将key相同的数据聚在一起,Hadoop采用了基于排序的策略。由于各个MapTask已经实现对自己的处理结果进行了局部排序,因此,ReduceTask只需对所有数据进行一次归并排序即可
(3)Reduce阶段:reduce()函数将计算结果写到HDFS上

10 MapTask & ReduceTask源码解析

=================== MapTask ===================
context.write(k, NullWritable.get()); //自定义的 map 方法的写出,进入
	output.write(key, value);
		//MapTask727 行,收集方法,进入两次
		collector.collect(key, value,partitioner.getPartition(key, value, partitions));
			HashPartitioner(); //默认分区器
		collect() //MapTask1082 行 map 端所有的 kv 全部写出后会走下面的 close 方法
			close() //MapTask732 行
				collector.flush() // 溢出刷写方法,MapTask735 行,提前打个断点,进入
					sortAndSpill() //溢写排序,MapTask1505 行,进入
						sorter.sort() QuickSort //溢写排序方法,MapTask1625 行,进入
					mergeParts(); //合并文件,MapTask1527 行,进入
				collector.close(); //MapTask739 行,收集器关闭,即将进入 ReduceTask
=================== ReduceTask ===================
if (isMapOrReduce()) //reduceTask324 行,提前打断点
initialize() // reduceTask333 行,进入
init(shuffleContext); // reduceTask375 行,走到这需要先给下面的打断点
	totalMaps = job.getNumMapTasks(); // ShuffleSchedulerImpl 第 120 行,提前打断点
 		merger = createMergeManager(context); //合并方法,Shuffle 第 80 行
			// MergeManagerImpl 第 232 235 行,提前打断点
			this.inMemoryMerger = createInMemoryMerger(); //内存合并
			this.onDiskMerger = new OnDiskMerger(this); //磁盘合并
rIter = shuffleConsumerPlugin.run();
		eventFetcher.start(); //开始抓取数据,Shuffle 第 107 行,提前打断点
		eventFetcher.shutDown(); //抓取结束,Shuffle 第 141 行,提前打断点
		copyPhase.complete(); //copy 阶段完成,Shuffle 第 151 行
		taskStatus.setPhase(TaskStatus.Phase.SORT); //开始排序阶段,Shuffle 第 152 行
	sortPhase.complete(); //排序阶段完成,即将进入 reduce 阶段 reduceTask382 行
reduce(); //reduce 阶段调用的就是我们自定义的 reduce 方法,会被调用多次
	cleanup(context); //reduce 完成之前,会最后调用一次 Reducer 里面的 cleanup 方法

11.MapReduce开发总结

	1、InputFormat
		1)默认的是TextInputformat  kv  key偏移量,v :一行内容
		2)处理小文件CombineTextInputFormat 把多个文件合并到一起统一切片
	2、Mapper 
		setup()初始化;  map()用户的业务逻辑; clearup() 关闭资源;
	3、分区
		默认分区HashPartitioner ,默认按照key的hash值%numreducetask个数
		自定义分区
	4、排序
		1)部分排序  每个输出的文件内部有序。
		2)全排序:  一个reduce ,对所有数据大排序。
		3)二次排序:  自定义排序范畴, 实现 writableCompare接口, 重写compareTo方法
			总流量倒序  按照上行流量 正序
	5、Combiner 
		前提:不影响最终的业务逻辑(求和 没问题   求平均值)
		提前聚合map  => 解决数据倾斜的一个方法
	6、Reducer
		用户的业务逻辑;
		setup()初始化;reduce()用户的业务逻辑; clearup() 关闭资源;
	7、OutputFormat
		1)默认TextOutputFormat  按行输出到文件
		2)自定义
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值