第六天 hadoop MapReduce运行原理

一、MapReduce概述

<1>概念

分布式计算框架,解决大数据计算问题
在集群上并发的计算运行分布式程序		
分布式、海量数据的计算框架
		单机版:资源有限(磁盘、内存、CPU)、存储能力、处理能力
		分布式版:
			多台计算机存储海量的数据  (HDFS思想存储大数据)
			计算:
				1. 并发计算
				2. 汇总结果数据
				3. 上面的两个阶段如果协调工作,如何启动运行,数据源如何查找... ...
				4. 业务逻辑(统计每个单词的次数)

			MapReduce: 分布式的运算框架,只需要关注业务逻辑的实现和处理
				1.核心功能:将用户的编写的业务逻辑代码  和 框架自带的组件整合成一个完成分布式运算程序,
						 并发的运行在一个hadoop集群上。
			
				2.阶段的划分:
					Map(并发任务)   +   Reduce (汇总任务)
			
				3.处理流程:		

<2>特点

处理大数据(处理PB级别)
容灾容错 (把挂掉的机器上的计算任务,转移给其他节点)	
速度慢 (离线数据分析)
扩展性强(通过添加新的节点,实现功能的扩展)

<3>底层的实现流程

一共两个阶段:map阶段+reduce阶段
map:
	允许并发操作(例如:10个maptask并发执行)
	按行读数据
	将数据切割
	存储(k-v)
	将结果传递给reduce

reduce:
	处理的数据从map阶段获取
	允许并发操作(例如:10个reducetask并发执行)
	按序分组
	统计
	输入结果	

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

<4> MapReduce案例----wordCount

统计1000个文件中的所有的字母出现的次数
思路
编写MR工程的流程:
		配置程序的运行信息 + 业务逻辑代码
		
		1. 启动类(包含main方法),启动作业job
			整个程序需要使用一个Driver来进行提交,提交的内容是:描述了各种必要参数配置的job对象

			1.1 job在启动前,设置相关的必要的属性

			例如:指定任务MapTask / ReduceTask  ,指定操作数据源,结果数据源,设置reduce的个数,设置运行模式 config... ...
			
				job.setMapperClass(xxxx.class);
				job.setReducerClass(xxxx.class);
				
			1.2 提交作业
				
				
		2. 自定义Mapper类,继承Mapper类
			2.1 指定Mapper的输入数据的kv形式(kv类型自定义,推荐使用hadoop提供的数据类型)	
				数据的传输( 要求数据序列化:使用java的序列化 / 使用hadoop提供的序列化 [轻量的、高效的]  )	

				Mapper类中接受的数据形式:
					第一行数据:hello world tom     ,k-v,    发送给Mapper:key=0  ,value=hello world tom
					第二行数据:tom hello nice      ,k-v,    发送给Mapper:key=17 ,value=tom hello nice 
					... ... ... .... ..				

			2.2 指定Mapper的输出数据的kv形式	
					
			2.3 重写map方法 [ 一个MapTask进程 ] ,对每个输入的kv调用一次 (一行)

		3. 自定义Reducer类,继承Reducer类
			3.1 指定Reducer接受到的数据的kv形式(kv类型自定义,推荐使用hadoop提供的数据类型)	
				数据的传输( 要求数据序列化:使用java的序列化 / 使用hadoop提供的序列化 [轻量的、高效的]  )	

				指定的kv类型是Mapper输出的kv类型
					k----v迭代器
				
			3.2 指定Reducer输出的数据的kv形式,持久化到本地的数据类型
				
			3.3 重写reduce方法 [ 一个Reducer进程 ] ,	对一组相同的key的kv组进行一次调用(一组)

		4. 问题
			运行:Output directory file:/e:/results already exists 错误
			job作业的输出路径不能存在:Output directory	

			解决:
				判断实参个数,大于1删除参数2

		5. 部署运行
			本地:配置hadoop路径
			hadoop集群:
				1. 将编写的工厂打包成jar包
				2. 导入linux中
				3. 使用hadoop jar 命令将当前的jar包提交到hadoop提群的yarn上运行
				4. 输入和输出路径都在hadoop集群上
Map端
/**
 * 定义输出和输出数据的类型:
 * 
 * 
 * KEYIN,  输入的数据的类型(按行),行号 ,long
 * VALUEIN,  输入的数据的value的类型  ,一行数据,字符串
 * KEYOUT,  输出的数据的类型, 单词,字符串
 * VALUEOUT , 输出的数据的value的类型,1,数字
 *
 *
 *序列化问题:
 *	数据格式、数据的传输效率、数据的大小
 *
 *	java序列化:重量级的序列化,体积较大,传输速率低
 *
 *	Hadoop提供了序列化机制:轻量级、高效的
 *		int   -------  IntWritable
 *		String ------  Text
 *		long ------ 	LongWritable
 *
 */
public class CountMap extends Mapper<LongWritable, Text, Text, IntWritable>{

	@Override
	protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context)
			throws IOException, InterruptedException {
		//1. 获取当前行数据
		String lineStr = value.toString();
		System.out.println("line:"+lineStr);
		
		//2. 切割
		String[] words = lineStr.split(" ");
		
		//3. 返回数据
		for (String w : words) {
			context.write(new Text(w), new IntWritable(1));
		}
		
	}
	
}
Reduce 端
public class CountReduce extends Reducer<Text, IntWritable, Text, IntWritable> {

	@Override
	protected void reduce(Text text, Iterable<IntWritable> it,
			Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
		int counts = 0;
		//1. 统计每一个单词的次数
		for (IntWritable count : it) {
			counts +=  count.get();
		}	
		//2. 返回结果
		context.write(text, new IntWritable(counts));
	}

}

client端
/**
 * 程序的入口
 *
 */
public class DriverClient {

	public static void main(String[] args) throws Exception {
		//1. 获取统计单词次数的任务
		Configuration conf = new Configuration();
		Job cwJob = Job.getInstance(conf);
		
		//2. 设置jar加载的路径
		cwJob.setJarByClass(DriverClient.class);
		
		//3. 加入map和reduce
		cwJob.setMapperClass(CountMap.class);
		cwJob.setReducerClass(CountReduce.class);
		
		//4. 设置输出和输入数据的类型
		cwJob.setMapOutputKeyClass(Text.class);
		cwJob.setMapOutputValueClass(IntWritable.class);
		
		cwJob.setOutputKeyClass(Text.class);
		cwJob.setMapOutputValueClass(IntWritable.class);
		
		//5. 设置数据的来源和输出
		//map可以接受和处理多个文件的数据
		FileInputFormat.setInputPaths(cwJob, new Path(args[0]));
		FileOutputFormat.setOutputPath(cwJob, new Path(args[1]));
		
		//6. 提交任务,执行任务
		cwJob.submit();
		//cwJob.waitForCompletion(true);
		
	}
}

二、MapReduce运行流程

1. 在MapReduce作业中的进程

1. MRAppMaster : 负责整个程序在执行过程中的调度和状态的协调(yarn节点上)
2. YarnChild : 负责map阶段的整个数据处理流程   ,maptask
3. YarnChild : 负责reduce阶段的整个数据处理流程  ,reducetask

2. mr程序执行流程

1. 启动mr程序,最先启动的进程是MRAppMaster , 它启动后根据本次job的描述信息,
   计算出当前需要使用的maptask的实例个数,向集群申请启动响应数据量的maptask进程

2. maptask进程启动后,根据给定的数据切片 (具体是哪个文件中的哪块区域数据) 范围进行数据的处理
	处理流程:
		2.1 利用客户端指定的InputFormat来获取 RecordReader 需要读取的数据,形成kv格式
		2.2 将输入的kv对传递给   客户端   指定的map类的map方法, map方法实现业务处理,将map方法处理的结果以kv对形式输出缓存中
		2.3 将缓冲中的kv对按照  k  进行分区排序,不断写入磁盘文件

3. MRAppMaster监控到所有的maptask 任务进程执行完毕后,根据 客户端指定的参数  启动相对应数量的reducetask进程
   MRAppMaster告知reducetask处理的数据的范围和位置
			
4. reducetask 进程启动后,根据MRAppMaster告知的待处理数据的位置,从maptask所运行机器上获取多个maptask的输出结果
	重新对接受到数据进行合并(根据key)和排序,根据相同的key进行kv对的分组,调用用户重写的reduce方法 实现具体的业务	
	计算出处理结果,将处理结果返回给   客户端指定的OutFormat进行数据的持久化
	

MapReduce:job
maptask:任务
reducetask:任务

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

MrAppMaster 配置作业job配置参数,
					maptask  并发度:文件切片 , 向集群申请maptask进程
						FileInputFormat, computeSplitSize(){
										 max(min,min(max,         blocksize)) //128MB   //以文件为单位}
											  1   long最大值        128M
											
		maptask进程:
			1. job配置操作的数据的位置:Inputformat(FileInputFormat),RecoredReader读取切片数据,将数据以kv交给maptask
			2. 接受数据,执行map方法,实现数据的操作
			3. 将结果数据以kv形式写出(任务队列:map并发执行,map全部执行完毕后,reduce才执行)
			   将结果数据先存放到内存中(缓冲区)(排序,分组),空间有限
			   缓冲区达到80%,将数据持久化到磁盘(空间较大)临时目录
			   
		MrAppMaster监听到所有的maptask进程执行完毕后,启动reduce进程
			1. reduce接受处理数据	(从临时目录中获取map存储数据)
			2. 将结果数据先存放到内存中(缓冲区)(排序,分组),空间有限
			   缓冲区达到80%,将数据持久化到磁盘(空间较大)临时目录	
			3. 执行reduce方法,将结果返回	(job作业的配置参数中指定的输出位置)				

在这里插入图片描述

二、MapReduce 并行度 决定机制

MapTask并行度 决定机制

并行度   map阶段的任务处理时的并发程度(影响job的处理速度)

一个job的map的并行度 由客户端在提交job时 已经确定了,FileInputFormat类中的getSplits()方法,对数据源数据进行分片

具体的个数:根据 待处理数据的 逻辑切片 (split)   来决定 ,每一个split都会分配一个maptask进程

ReduceTask的并发度决定机制

数据时由用户可以直接指定:默认值是1
	job.setNumReduceTask(num) 	

如果数据分配不均匀,在reduce阶段会发生数据的倾斜,根据具体业务需求来进行设定

三、切片机制

FileInputFormat中使用切片方式:
	默认采用    数据块大小   来切割(逻辑上),默认大小等于   block的大小(128m)

	以  文件  为单位,针对每一个文件单独切片

切片大小:FileInputFormat中的computeSplitSize(blocksize,minsize,maxsize)	的方法决定	
	blocksize默认值:128M
		
例子:
	file1.txt   250M
	file2.txt	100M	
  
	调用getSplits()方法处理数据源,获取切片信息List集合:
		f1-split1   0-128M
		f1-split2   129-250M
		f2-split1   0-100M

在这里插入图片描述

切片优化

1. 问题:
	默认情况下,使用的TextInputFormat  ,切片的大小是128M	
	  protected long computeSplitSize(long blockSize, long minSize, long maxSize) {
	    	return Math.max(minSize, Math.min(maxSize, blockSize));
	  }
	
	切片机制:以文件为单位,小文件之间不会合并

	切片个数多,maptask进程多,处理数据少,效率低

2. 解决的思想:
	在maptask处理数据前,在输入流读取数据前,将小文件进行合并

3. 实现:
	使用Hadoop提供的API:CombineTextInputFormat ,不使用系统默认的 TextInputFormat
	CombineTextInputFormat作用:  逻辑上  的  合并小文件

4. 案例:
	有三个小文件:words.txt,words2.txt,words3.txt
	默认情况下  : maptask /  maptask   /   maptask 
	实现的效果:              maptask
	
	//设置切片的大小 //取消使用TextInputFormat
	cwJob.setInputFormatClass(CombineTextInputFormat.class);
	CombineTextInputFormat.setMaxInputSplitSize(cwJob, 2048);
	CombineTextInputFormat.setMinInputSplitSize(cwJob, 1024);

四、数据的分区

1. 现象:分区
	默认分区个数是reducetask的个数,默认的个数是1 (reduce默认的个数是 1)
	
	//设置reduce的个数:
		cwJob.setNumReduceTasks(3); // 创建多个分区

	分区中的内容的存放规则:根据对key求hash%分区的个数 
	reduce从对应的一个分区中提取数据,执行后续操作

2. 问题:无法设定规则,不灵活

3. 需求:统计单词出现的次数,
		首字母: 将a-g交给一个reduce处理,将h-n交给一个reduce处理,将o-z交给reduce处理
		
4. 实现:
	通过Hadoop提供的API: Partitioner分区	
	控制数据存放的分区

5. 案例:
	首字母: 将a-g交给一个reduce处理,将h-n交给一个reduce处理,将o-z交给reduce处理

6. 实现步骤:
	1. 自定义类,继承Partitioner类,重写getPartition方法,此方法返回数据存放分区的具体位置
	2. 在作业类中设置使用自定义分区类(指定分区算法)
		//设置分区算法类
			cwJob.setPartitionerClass(CountPartitioner.class);
		//设置reduce的个数:
			cwJob.setNumReduceTasks(3);	 //分区算法个reducetask的个数是密切相关的
		
	3. 注意:  
		1<reduceTask的个数<Partitioner指定分区的个数  ,报错的

五、数据的排序

1. 现象:
	在MapReduce中都会对数据的进行排序,默认操作,排序的规则:根据key的字典顺序
	
	MapTask中:将处理结果存储到缓冲区中,在将数据移动到磁盘文件中之前对所有的数据进行排序		
			  map中所有的数据处理完毕后,全部存储到磁盘的文件中后,对所有文件进行一次合并

2. 需求:按照总流量降序显示数据
	88
	1111111
	10000000000

3. 解决:
	自定义排序规则
	操作自定义对象:实现序列化结构 / writable   / writableComparable 接口 

	重写此writableComparable 接口的compareTo方法,指定排序规则
		//自定义:使用一个字段设定排序规则
		//自定义:使用多个字段设定排序规则

六、数据的合并

在每一个maptask将输出数据写入磁盘前,对数据进行合并(按照相同的key)
reducetask接受所有的maptask后合并操作(按照相同的key)

1. 需求:在reduce之前先进行合并操作
2. 自定义合并类:Hadoop提供的API , Combiner ,本质上是一个Reducer
3. 实现步骤:
		1. 创建一个用户自定义类,继承 Reducer
		2. 重写方法reduce,汇总
		3. 输出结果数据
		4. 在作业job中设置用户自定义的合并类
			
4. 执行时机
	combiner先执行,reduce后执行

5. 案例:
	减少reduce读取的操作数据的条数,减轻reduce压力

在这里插入图片描述

七、案例 单词统计

Map端
/**
 * 定义输出和输出数据的类型:
 * 
 * 
 * KEYIN,  输入的数据的类型(按行),行号 ,long
 * VALUEIN,  输入的数据的value的类型  ,一行数据,字符串
 * KEYOUT,  输出的数据的类型, 单词,字符串
 * VALUEOUT , 输出的数据的value的类型,1,数字
 *
 *
 *序列化问题:
 *	数据格式、数据的传输效率、数据的大小
 *
 *	java序列化:重量级的序列化,体积较大,传输速率低
 *
 *	Hadoop提供了序列化机制:轻量级、高效的
 *		int   -------  IntWritable
 *		String ------  Text
 *		long ------ 	LongWritable
 *
 */
public class CountMap extends Mapper<LongWritable, Text, Text, IntWritable>{

	@Override
	protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context)
			throws IOException, InterruptedException {
		//1. 获取当前行数据
		String lineStr = value.toString();
		System.out.println("line:"+lineStr);
		
		//2. 切割
		String[] words = lineStr.split(" ");
		
		//3. 返回数据
		for (String w : words) {
			context.write(new Text(w), new IntWritable(1));
		}
		
	}
	
}

combine 端

public class CountCombiner extends Reducer<Text, IntWritable, Text, IntWritable> {
	
	Text t = new Text();
	IntWritable iw =new IntWritable();
	@Override
	protected void reduce(Text key, Iterable<IntWritable> values,
			Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
		int n = 0;
		//1.合并操作
		for (IntWritable intWritable : values) {
			n += intWritable.get();
		}
		t.set(key);iw.set(n);
		//2.输出结果
		context.write(t,iw);
	}

}

partition 端
/**
 * 设置分区规则
 */
public class CountPartitioner extends Partitioner<Text, IntWritable> {

	@Override
	public int getPartition(Text key, IntWritable value, int numPartitions) {
		//将a-g交给一个reduce处理,将h-n交给一个reduce处理,将o-z交给reduce处理
		int index = 2;
		//1. 获取key
		String word = key.toString().toLowerCase();
		//2. 获取可以首字母
		char firstChar = word.charAt(0);
		//0----48
		//A----65
		//a----97
		
		//3. 判断当前单词存放的分区index
		if(firstChar>=97&&firstChar<='g'){
			index = 0;
		}else if(firstChar>='h'&&firstChar<='n'){
			index = 1;
		}
		System.out.println(word+"--->"+firstChar+"----->index:"+index);
		return index;
	}

}
Reduce 端
public class CountReduce extends Reducer<Text, IntWritable, Text, IntWritable> {

	@Override
	protected void reduce(Text text, Iterable<IntWritable> it,
			Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
		int counts = 0;
		//1. 统计每一个单词的次数
		for (IntWritable count : it) {
			counts +=  count.get();
		}	
		//2. 返回结果
		context.write(text, new IntWritable(counts));
	}

}

client 端
/**
 * 程序的入口
 *
 */
public class DriverClient {

	/**
	 * 
	 * e:/test e:/results
	 * 
	 * 
	 * args 接受,程序在运行传递的实参
	 * 	args[0] = e:/test
	 *  args[1] = e:/results (不能存在的)
	 */
	public static void main(String[] args) throws Exception {
		//1. 获取统计单词次数的任务
		Configuration conf = new Configuration();
		
		//输出路径的判断
		if(args.length>1){
			//删除指定的输出路径
			FileSystem.get(conf).delete(new Path(args[1]));
		}
		
		Job cwJob = Job.getInstance(conf);
		
		//2. 设置jar加载的路径
		cwJob.setJarByClass(DriverClient.class);
		
		//3. 加入map和reduce
		cwJob.setMapperClass(CountMap.class);
		cwJob.setReducerClass(CountReduce.class);
		
		//4. 设置输出和输入数据的类型
		cwJob.setMapOutputKeyClass(Text.class);
		cwJob.setMapOutputValueClass(IntWritable.class);
		
		cwJob.setOutputKeyClass(Text.class);
		cwJob.setMapOutputValueClass(IntWritable.class);
		
		//5. 设置数据的来源和输出
		//map可以接受和处理多个文件的数据
		FileInputFormat.setInputPaths(cwJob, new Path(args[0]));
		FileOutputFormat.setOutputPath(cwJob, new Path(args[1]));
		
		//设置切片的大小 //取消使用TextInputFormat
		cwJob.setInputFormatClass(CombineTextInputFormat.class);
		CombineTextInputFormat.setMaxInputSplitSize(cwJob, 2048);
		CombineTextInputFormat.setMinInputSplitSize(cwJob, 1024);
		//23 + 1000  + 2000
		//23+1000=1023+1 = 1024  /  1999
		
		//设置分区算法类
		cwJob.setPartitionerClass(CountPartitioner.class);
		
		//设置reduce的个数:
		cwJob.setNumReduceTasks(4);
		
		//设置合并操作
		cwJob.setCombinerClass(CountCombiner.class);
		
		//6. 提交任务,执行任务
//		cwJob.submit();
		cwJob.waitForCompletion(true);
		
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值