MapReduce杂记

学习摘要:

学习阶段,又不对的地方请指出,不胜感激。

/**
MapReduce  程序编写规范:
1、用户编写的程序分成三个部分:Mapper,Reducer,Driver(提交运行 MR 程序的客户端)
2、Mapper 的输入数据是 KV 对的形式(KV 的类型可自定义)
3、Mapper 的输出数据是 KV 对的形式(KV 的类型可自定义)
4、Mapper 中的业务逻辑写在 map()方法中
5、map()方法(maptask 进程)对每一个<K,V>调用一次
6、Reducer 的输入数据类型对应 Mapper 的输出数据类型,也是 KV 对的形式
7、Reducer 的业务逻辑写在 reduce()方法中
8、Reducetask 进程对每一组相同 k 的<K,V>组调用一次 reduce()方法
9、用户自定义的 Mapper 和 Reducer 都要继承各自的父类
10、整个程序需要一个 Drvier 来进行提交,提交的是一个描述了各种必要信息的 job 对象


MapReduce的运行机制:
一个完整的MapReduce程序在分布式运行时有两类实例进程:
1、MRAppMaster:负责整个程序的过程调度及状态协调;
2、Yarnchild:负责map阶段的整个数据处理流程;
3、Yarnchild:负责reduce阶段的整个数据处理流程;
以上两个阶段MapTask和ReduceTask的进程都是YarnChild,并不是说这个MapTask和ReduceTask就跑在同一个YarnChild进程里;


Mapreduce程序的运行流程:
1、一个MR程序启动的时候,最先启动的是MRAppMaster,MRAppMaster启动后根据本次job的描述信息,计算出需要的mapTask实例数量,
然后向集群申请机器启动相应数量的maptask进程。
2、maptask进程启动之后,根据给定的数据切片(哪个文件的哪个偏移量范围)范围进行数据处理,主体流程为:
	A、利用客户指定的InputFormat来获取RecordReader读取数据,形成输入KV对
	B、将输入KV对传递给客户定义的map()方法,做逻辑运算,并将map()方法输出的KV对收集到缓存;
	C、将缓存中的KV对按照K分区排序后不断溢写到磁盘文件。
3、MRAppMaster监控到所有maptask进程任务完成之后(真实情况是,某些maptask进程处理完成后,
就会开始启动reducetask去已完成的maptask处fetch数据), 会根据客户指定的参数启动相应数量的reducetask进程,并告知reducetask进程要处理的数据范围(数据分区)
4、Reducetask进程启动之后,根据MRAppMaster告知的待处理数据所在位置,从若干台maptask运行所在机器上
获取到若干个mapTask输出结果文件,并在本地进行重新归并排序,然后按照相同key的KV为一组,
调用客户定义的reduce()方法进行逻辑运算,并收集运算输出的结果KV,然后调用客户指定的OutputFormat将结果输出到外部存储。


MapTask并行度决定机制:
maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度。

一个job的map阶段并行度由客户端在提交job时决定,客户端对map阶段并行度的规划的基本逻辑为:
将待处理数据执行逻辑切片(按照一个特定切片大小,将待处理数据划分成逻辑上的多个split),
然后每一个split分配一个mapTask并行实例处理。

对于获取切片的方式:FileInputFormat.getSplits()实现类的getSplit()完成的。
该方法返回的是List<InputSplit>, InputSplit封装了每一个逻辑切片的信息,包括长度和位置信息,
而getSplits()方法返回一组InputSplit.


切片机制:
FileInputFormat中默认的切片机制:
1、简单的按照文件的内容长度进行切片;
2、切片大小,默认等于block大小;
3、切片时,不考虑数据集整体,而是逐个针对每个文件单独切片;
eg:
File1.txt 200M
File2.txt 100M
经过getSplit()方法处理之后,形成的切片信息是:
File1.txt-split1  0--128M
File1.txt-split2  128--200M
File2.txt-split1  0--100M

FileInputFormat中切片的大小的参数配置:
通过分析源码,在FileInputFormat中,计算切片大小的逻辑:
long splitsSize = computeSplitSize(blockSize,minSize,maxSize)  求这三个值的中间值大小。
切片主要由这几个值来运算决定:
blockSize:默认的是128M,可通过dfs.blocksize修改;
minSize:默认的是1,可以通过mapreduce.input.fileinputformat.split.minsize 修改;
maxSize:默认的是Long.MaxValue,可以通过mapreduce.input.fileinputformat.split.maxsize修改;

无论怎么调,都不能让多个小文件"划入"一个split。


MapTask并行度经验之谈:
如果硬件配置为2 * 12core + 64G,恰当的map并行度是大约每个节点20-100个map,最好每个map的执行时间至少一分钟;
1、如果每一个task运行的时间都只有30-40秒,那么就减少该job的map或者reduce数目;
   因为每一个task的setup和加入到调度器中进行调度,这个中间的过程可以都要花费几秒钟,
   所以如果每个task都非常快的跑完了,就会在task的开始和结束的时候浪费太多的时间。
   
   配置task的JVM重用可以改善这个问题;
   mapred.job.reuse.jvm.num.tasks, 默认的是1,表示一个JVM上最多可以顺序执行的task数目为1;
   设置成多个也是排队顺序执行的。
  
2、如果input的文件非常大,eg:1TB, 可以考虑将hdfs上的每个blockSize设大,比如设成256MB或者512M



ReduceTask并行度决定机制:
reduceTask的并行度同样影响整个job的执行并发度和执行效率,但与maptask的并发数有切片决定不同,
reduceTask数量的决定是通过参数设定的,可以手动的修改;
在代码中设定:(默认的是1)
job.setNumReduceTasks(4);

设置为0表示不运行reduceTask任务,也就没有reducer阶段;

如果数据分布不均匀,就有可能在reduce阶段产生数据倾斜;
注意:reduceTask数量并不是任意设置的,还要考虑业务逻辑需求,
在有些情况下,需要计算全局汇总结果,就只能有一个reduceTask。
尽量不要运行太多的reduceTask。对大多数job来所,最好reduce的个数最多和集群中的reduce持平,
或者比集群的reduce slots小。
   
动态规划思想:   
现在要爬楼梯到10层,可以由什么方式上到第10层?
f(10) = f(9) + f(8)
f(9) = f(8) + f(7)
......
f(3) = f(1) + f(2);
f(2) = f(1) + f(0)


f(n) = f(n-1) + f(n-2)




List<InputSplit> splits = FileInputFormat.getSplits(JobContext context);
List<FileSplit> splits = FileInputFormat.getSplits(JobContext context);
makeSplit(... splitsize ...) === 构造一个FileSplit


对应关系:
一个Container -- 一个Task  --- 一个JVM进程
一个ReduceTask -- 一组key-value  -- reduce方法的调用次数


默认情况下:一个数据块 --- 一个MapTask  --- 一个FileSplit(文件切片)
FileSplit : 封装了一个数据块的各种必要信息:副本个数,副本存放地,blocksize, 文件名,起始结束偏移量
InputSplit --- FileSplit
InputSplit : 顶级接口  
FileSplit : 是InputSplit的一个针对文件的实现类,一个切片


对应关系:splits.size() == 一个mapReduce程序的总mapTask数
其实每次启动给一个mapTask,那么当前这个节点,都会远程的去获取到MRAppMaster主控程序解析出来的对应的
FileSplit对象, 每个mapTask的启动都需要一个FileSplit对象
在默认情况下:splitSize = blocksize




mapreduce的11大组件:
1、Mapper:映射,提取数据,做成key-value对输出,Mapper阶段要知道MapTask的数目是怎么确定的,FileInputFormat.getSplit()方法,
   该方法通过三个值确定每一个分片的大小:min,blockSize,max 通常情况下我们选择的是三个数的中间值
   InputSplit >> FileSplit >> maptask >> JVM进程
  
2、Reducer:聚合输出,注意reduceTask个数的确定,通过setNumReduceTask()设定,如果不是使用设置为0,
   Reducer端fetch maptask阶段产生的数据。
   
3、partitioner:自定义分区组件,默认使用的HashPartitioner,根本还是使用的hash散列,就是对map端的输出根据key的hash值
				根据 key 的 hashcode%reducetask 数来分发进行分区;我们可以制定自己的分区规则,
				但是我们在自定义分区组件中设置的分区个数要小于等于设置的ReduceTask的个数;


4、soter:自定义排序组件 在二次排序的时候可能会使用sortComparator自定义排序规则进行排序;
		  如果我们使用的输入输出对象是POJOBean的情况,我们也可以在POJOBean中实现WritableCompatable接口
		  然后实现comparaTo()方法,实现比较排序;
		  只要有reducer阶段,就会有排序;	在整个shuffle阶段,应该有三次排序,排序算法一般使用的归并算法,,按理来说应该实现Comparable接口,
		  但是我们结合下面的序列化,所以实现了其一个子接口 WritableComparable,
		  该接口继承了Writable和Comparable两个接口;


5、combiner:自定义局部合并组件,默认情况设置的是Reducer,我们可以自定实现,主要发生在mapTask阶段					,
			 而且合并是对每一个mapTask来说的,对于整体没有进行合并,这也是Combiner组件的一个重要原则; 另一个重要的原则是:Reducer阶段的输入和输出的数据类型要一致,
在特定情况下使用combiner组件能够对mapper端的输出做一个局部合并,减少shuffle阶段reducer处理的数据量
       		 从而提高了效率。
			 
			 Combiner 的输出 kv 类型应该跟 Reducer 的输入 kv 类型对应起来
			 Combiner 的输入 kv 类型应该跟 Mapper 的输出 kv 类型对应起来
			 
		 
6、WritableComparable:该组件主要体现在上面提到的自定义输入输出类型pojo类,
					   完成自定义输入输出bean类型的序列化和反序列化而且可以重写comparaTo方法,完成对输出数据的排序。
			           重点是write()方法和readFields()方法的out和in所对应的字段一定要一样!!!			 
			 
                       序列化框架在反序列化操作创建对象实例时会调用无参构造 
                       注意:  字段的反序列化顺序与序列化时的顺序保持一致, 並且类型也一致
					   
7、GroupComparator:自定义分组组件,对于我们做分组取TopN的时候很有用处,我们通过该组件可以设定我们自己的分组规则;
 					要注意的是如果我们没有定义该组件,并且输入输出类型为bean的时候,那么整个过程的分组和排序规则都是
 					通过compataTo方法中指定的字段进行操作的;
 					所以我们在自定该组件的时候继承WritableComparator类,并且重写compare()方法,该方法通常情况下
 					传入的两个对象是我们自定义的分组组件对象,那么我们就要将其强转成两个我们需要比较并按其某个属性分组的对象
 					这个对象也是WritableComparable的实现类,注意的是:我们要构建一个构造方法,调用其父类中的构造
 					方法super(AimBean.class,true); 这样才能实例化一个对象;
 					值得一提的是,使用自定义分组组件指定的分组字段,一定要在comparaTo方法中使用字段得而前面
 					eg:	a
 						a b
 					    a b c
 						a b c d
 						a b c d e 
 					即:如果排序字段使用的a,那么分组字段使用的只能是a;
 					        如果排序字段使用的b,那么分组字段使用的只能是a或则a b
 					        如果排序字段使用的e,那么分组字段使用的只能是a或则a ab abc abcd abcde这五种情况。
 						
 8、InputFormat
 9、RecordRead
 10、OutputFormat
 11、RecordWriter	




 关于Join操作的实现:
 MapReduce 的 Join 操作主要分两类:MapJoin 和 ReduceJoin
先看 ReduceJoin:
	1、 map 阶段,两份数据 data1 和 data2 会被 map 分别读入,解析成以链接字段为 key 以查
	    询字段为 value 的 key-value 对,并标明数据来源是 data1 还是 data2。
	2、 reduce 阶段,reducetask 会接收来自 data1 和 data2 的相同 key 的数据,在 reduce 端进
	    行乘积链接,最直接的影响是很消耗内存,导致 OOM
	如果做join实现的时候,用ReducerJoin产生了数据倾斜问题,那么一定要解决。
	
再看 MapJoin:
	MapJoin 适用于有一份数据较小的连接情况。做法是直接把该小份数据直接全部加载到内存
	当中,按链接关键字建立索引。然后大份数据就作为 MapTask 的输入,对 map()方法的每次
	输入都去内存当中直接去匹配连接。然后把连接结果按 key 输出,这种方法要使用 hadoop
	中的 DistributedCache 把小份数据分布到各个计算节点,每个 maptask 执行任务的节点都需
	要加载该数据到内存,并且按连接关键字建立索引。
	
	MapJoin的出现,就是为了避免数据倾斜。
	因为MapJOin中链接操作已经在maaper端完成了,那么就可以不用reducer阶段。
	因为并发的mapTask是基本上不会出现数据倾斜,就算出现了。 也不是很严重。
	
	
select  ... distinct... from ... join ...where ... group by  .... order by .... limit ....   
 
 
 
 ReduceJoin,而且ReduceJoin适用性比较强,无论是:大表对大表、大表对小表,小表对小表都可以
 但是我们在处理ReduceJoin的时候要考虑数据倾斜的问题;
 reducer端的Join我们通过setup()方法来进行初始化操作,获取对应路径下面的两个文件的文件名称,
 通过对文件名称的判断,分别读取两个文件中的数据,然后将数据解析添加特有的标记,
 然后将结果改造好的结果输出,reducer端进行解析,将两个文件中对应key的数据存储到两个集合List中,
 两层循环操作两个list中相匹配的数据,然后讲结果写出,每次迭代完成之后,讲两个List集合清空,
 然后再进行下一次的操作;
 
 关于Mapper端的Join,我们的思想是对小的数据进行缓存,在MR程序开始的前设定其读取的位置,
 然后通过setup()方法,将指定位置的小文件加载都运行大文件Block块的节点,
 然后进行操作,这里会有一个资源分发策略,主要是将资源分发给指定操作的节点
 首先我们在驱动程序中设置小文件的缓存路径:job.addCacheFile(URI)这里使用的这种情况,使得程序只能在集群中运行;
 设置完成之后,我们在mapper中的setup()方法中通过context这个万能对象进行获取路径context.getLocalCacheFiles();
 这样我们就能够获取到小文件的路径,
 然后通过字符流的形式去读文件:BufferedReader br = new BufferedReader(new FileReader(strPath));
 然后进行逐行去读:br.readLine(),然后进行截取操作,最后关闭流
 还有一个重要的操作:我们通常根据某个字段和大文件中的数据进行匹配操作,
 所以我们使用一个map来存储小文件解析出来的数据;关联字段作为map的key其余的字段作为map的value,
 然后我们就可以在map()方法中进行操作,按行切分读取大文件中的内容,然后使用关联字段取map中判断有没有这个key,
 如果有,我们就去出来和大文件的相应的字段进行合并操作,然后将合并擦操作后的数据输出,
 这样就完成了map端的Join操作。 




 
多Job串联执行的关键是在驱动方法中的配置:
ControlledJob sumcj = new ControlledJob(jobsum.getConfiguration());
ControlledJob sortcj = new ControlledJob(jobsort.getConfiguration());


sumcj.setJob(job1);
sortcj.setJob(job2);


//  设置作业依赖关系
sortcj.addDependingJob(sumcj);


JobControl jc = new JobControl("flow sum and sort");


jc.addJob(sumcj);
jc.addJob(sortcj);


Thread jobThread = new Thread(jc);
jobThread.start();


while(!jc.allFinished()){
	Thread.sleep(500);
}
jc.stop();




MapReduce全局计数器:
计数器是用来记录 job 的执行进度和状态的。它的作用可以理解为日志。我们可以在程序的
某个位置插入计数器,记录数据或者进度的变化情况。


对 MapReduce 性能调优很有帮助,MapReduce 性能优化的评估大部分都是
基于这些 Counter 的数值表现出来的。


使用全局计算器统计文本行数和单词数:
// 统计行数,因为默认读取文本是逐行读取,所以 map 执行一次,行数+1
context.getCounter(CouterWordCountC.COUNT_LINES).increment(1L);
String[] words = value.toString().split(" ");
for(String word: words){
    // 统计单词总数,遇见一个单词就+1
    context.getCounter(CouterWordCountC.COUNT_WORDS).increment(1L);
}


全局排序:
	mapreduce   每个reduceTask的结果有序
	如果想实现全局有序,又想利用mapreduce实现  应该如何操作?
mapjoin的实现思路




1、TopN:肯定都是排序规则和分组规则不一致的。
	求每个班的最高成绩
	分组: 班
	排序: 班   成绩
	1、因为在默认情况下。compareTo方法即是排序规则,又充当分组规则
	2、当自定义排序规则和分组规则不一致时,就必须要自定义分组规则。
	3、编写一个分组规则器 继承 WritableComparator  
	4、分组规则的字段只能是排序规则字段的前几个


2、ReduceJoin
	优点:通用的join实现
	缺点:害怕数据倾斜


3、MapJoin
	优点:效率高  避免了Reducer阶段
	缺点:只适合大小表连接
	
Mapper的输入:
	1、直接告诉你:map方法的参数是数据读取组件逐行读取到的一行数据
		key == value在文件中的offset
		value === 读取到的一行数据
	2、map的方法的参数到底是怎么来的?
		谁在调用你的mapper组件的map方法,就由谁负责给map方法传参
	3、mapper组件的时候已经发一个一个问题:
		mapper中有四个方法:
		setup
		map
		cleanup
		run


		run(context){
			setup(context);
			while(context.nextKeyValue()){
				map(context.getCurrentkey(), context.getCurrentValue(), context);
			}
			cleanup(context);
		}


		到底谁在调用map方法?
		发现一个结论:mapper组件中的run方法在调用map方法
	4、新的问题:谁在调用这个run方法?
	5、将来有可能会遇到新的需求不是逐行读取,读取自数据库
	6、将来有可能会遇到新的需求不是逐行输出,数数到数据库
		得掌握默认的逐行读取的具体实现是怎样的。
	
	最终的课程实现两方面的目的:
	1、观看源码实现来了解整个MapReduce编程套路
	2、学会自定义输入和输出组件


	入手问题:找出谁在调用run方法?
	因为找到了谁在调用ruN方法就找到了 context 对象!!!!!!


数据读取组件中的最重要方法是:lineRecordReader.nextKeyValue();
数据输出组件中的最重要的方法是:lineRecordWriter.writer();


textinputformat --- mapper  --- partitioner  --- combiner  ---- reducer  ---- textoutputformat


1、输入输出的源码解析:
A、输入流程


	入口:
		作为一个会编写MR程序的人来说,知道map方法的参数是默认的数据读取组件读取到的一行数据
		但是谁在读取? 但是谁在调用这个map方法?


	答案:
		谁在调用map方法:
			mapper.run(context){
				setup(context);
				while(context.nextKeyValue()){
					map(context.getCurrentKey(), context.getCurrentValue(), context);
				}
				cleanup();
			}
		
		谁在读取这个key-value:
			context.nextKeyValue();
		
	重要的方法总结:
		1、context.nextKeyValue();  			      		   
			负责读取数据,但是方法的返回值却不是读取到的key-value,
			而是返回了一个标识有没有读取到数据的布尔值
		2、context.getCurrentKey();  负责获取context.nextKeyValue() 读取到的key
		3、context.getCurrentValue();  负责获取context.nextKeyValue() 读取到的value
		4、context.write(key,value);   负责输出mapper阶段输出的数据


	
	新的切入点:


		1、谁在调用run方法
		2、context参数怎么来的,是什么


		
	共同答案:找到了谁在调用run方法,那么就能知道这个谁就会给run方法传入一个参数叫做:context




	一步步深入;


		1、最开始,mapper.run(context)是由mapTask实例对象进行调用


			mapTask.run(){
				runNewMapper(){
					mapper.run(mapperContext);
				}
			}


		2、能确定的是:mapperContext一定有上面说的那四个方法


		3、继续确定:


			mapperConext.nextKeyValue(){
				mapConext.nextKeyValue();
			}


		4、mapConext就是这个类MapContextImpl的实例对象


		5、继续确定:


			mapConext = new MapContextImpl(input)
			mapConext.nextKeyVlaue(){
			
				LineRecordReader real = input.createRecordReader();


				real.nextKeyValue();
			}


		6、最终的nextKeyValue方法的具体实现是在LineRecordReader中实现的


	
	强调重点;
		1、mapper.run(context)方法的具体实现
		2、real.nextKeyValue();
		
B、输出流程
	入口:
		void reduce(key, values, context){
			context.write(outKey, outValue);
		}


	小结论:
		我们在编写MR程序的时候,仅仅只是调用context.write(outKey, outValue);就输出了数据
		但是谁在调用这个方法? 我们得出结论是 reducer.run()在调用


	一步步深入:
		1、reducer.run()方法的定义是怎样的?
			reducer.run(context){
				setup(context);
				while(context.nextKey()){
					reduce(context.getCurrentKey(), context.getValues(), context);
				}
				cleanup(context);
			}


		2、新的问题:
			1、谁在调用reducer.run()?
			2、context.nextKey(),context.getCurrentKey(),context.getValues()怎么实现的?


		3、找出谁在调用reducer.run():
			reduceTask.run(){
				runNewReducer(){
					reducer.run(reducerContext);
				}
			}


		4、由此,我们确定:
			reducerContext中一定有我们想要的三个方法的实现:
			nextKey()
			getCurrentKey()
			getValues()
			write(key, value)


		5、最终前面三个方法的实现在:
			ReduceContextImpl类中有具体的实现
			了解分组器的调用就可以
			reducer阶段的数据读取逻辑:
				每次读取一个临时文件或者内存中的排序好了的数据中的一行
				用当前读取到的这一行数据和上一行进行比对,
				看是否按照用户指定的分组规则能比较出key是相同的
				如果是相同的,证明刚才读取到的这个key-value是之前的key_value是同一组
				如果不是相同的。则证明有下一个key-value, 不是同一组


		6、最终的最后一个方法write(key, value)的具体实现:
			是在默认的数据输出组件LineRecordWriter中的实现的
			lineRecordWriter.write(key,value){
				// 判断是否key为空
				// 判断是否value为空
				// 如果key和value都为空,则当前的方法直接返回,不进行任何输出
				// 如果key不为空,输出key
				// 如果key和value都不为空,输出key-value之间的分隔符
				// 如果value不为空,输出value
				// 输出换行符
			}


			write方法的内部输出数据其实是依赖于一个方法:
				write(key,value){
					writeObject(key);
					writeObject(value);
				}


			writeObject(){
				// 通过一个out对象往外写出一个字节数组
				// DataOutputStream out . write(byte[]);
			}		


2、自定义输入组件 
3、自定义输出组件


1、CombineFileInputFormat
	它是什么?  数据读取组件
	它是什么样的数据读取组件?   可以针对小文件进行合并读取的
	问题:
		如果一个mapreduce程序需要对100个1M的文件进行计算。
		那么最终会启动多少个mapTask?  100个


		既然每个mapTask处理的都是一个单个文件,但是由于这个文件特别小,
		就造成了,我们在集群上启动了100个JVM进程,但是
		每个进程都只计算了1M的数据造成资源浪费

		HDFS和MapReduce都声明:说当前这两个技术都是用来处理海量大文件的。
		不适合用来做大批量小文件的存储和计算,但是不表示不能做


		所以,如果遇到了这种需要对大批量小文件进行计算或者存储的场景:


		怎么解决?
		1、对于HDFS来说,它不适合存储大批量小文件的原因是因为需要占用过多的namenode的内存
			归档存储  ---  打包存储
		2、对于MapReduce来说,因为对于一个小文件的处理单独启动一个JVM进程,造成计算浪费,导致计算效率不高
			CombineFileInputFormat
			
	CombineFileInputFormat是怎么解决MR程序中的小文件合并到一个文件进行计算的?
	重点:就是改写了一个方法:CombineFileInputFormat.getSplits(JobContext);
	最大的不同:让一个FileSplit可以包含多个block


2、读写数据库
	在默认情况下,MR程序是读取数据来自文件系统的
	在默认情况下,MR程序是输出数据到文件系统
	而且都是HDFS文件系统
	因为MR程序本身就是运行在hadoop集群的。所以直接使用它自己的文件系统作为数据的支撑


	需求有变动:
		1、有可能需要读取的数据是存储在RDBMS中的
		2、有可能需要输出的最终结果是存储在RDBMS中的


	最简单结论:
		自定义数据的输入和输出规则


	MR编程框架已经意识到用户可能就有这种需求
	所以给用户就提供了实现


	具体实现:
	1、读数据库
		1、job.setInputFormatClass(DBInputFormat.class);
		2、指定数据库的位置
		3、指定从哪个表查询什么样的数据进行处理
		4、当前从数据库表中读取到的一堆数据中的一条记录,就会被封装成一个key-value
		5、所以你的数据库的record和map方法的参数key-value要建立对象关系,而且还要进行序列化和封装
		6、编写一个了自定义POJO类,必须要实现一个接口:DBWritbale接口
			class Student implements WritableComparable<Student>, DBWritable


	2、写数据库


遗留的问题:
	mapper阶段中的context.write(key,value);
最重要的问题:
	1、mapper阶段输出的数据到底去了哪里?
	2、mapper阶段输出的数据到底经历了一个怎么的处理流程被变成了reduce接收到的一组key-value?


1、入口:
	mapper.map(key,value, context){
		context.write(outKey,outValue);
	}

	
MapReduce流程中的shuffle过程中的partitioner和排序的真正作用是什么?
	Partitioner   根本目的就是让MR程序的第二个阶段,可以进行任务的切分并发
	排序    根本目的就是把相同的key的key-value聚集在一起
	Combiner    根本作用就是用来减少参与shuffle的数据量




MapReduce编程框架的中的所有组件的泛型都是一个key-value的形式。
	map:映射
	key    特征
	value      值
	其实就是读取数据,然后提取关键信息

	reducer阶段:
	其实就是对特征相同的值进行规约
	框架的最大的作用:
	在集群环境中,负责给用户搜集整个计算任务中的所有具有相同特征的值到一起进行规约

老版本的MapReduce不仅管任务调度,而且还管理资源调度
主从结构:JobTracker  +  TaskTracker 
不仅仅是为了分开而分开,其实也包含了部分MapReduce的设计不合理的情况:
1、资源管理的单位叫做  槽  slot   
		运行Task
		slot分为 map slot 和  reduce  slot
		map slot只能运行 mapTask
		reduce slot  只能运行 reduceTask
		假如一个节点能够抽象出来的slot有10个,
		map有6个,reduce有4个。
		由于reduceslot不能运行mapTASK任务,
		这个节点中最终能同时执行的mapTask的任务个数:6

2、以前的MapReduce程序的运行也会有一个主控程序,
	但是,所有job的主控程序MRAppMaster,全部都是运行在jobtracker这个节点
	
	
可以让我们的资源调度更高效
可以让我们的任务分配更智能
	解决方案;
		改进任务的调度和资源的抽象方案

	第一个问题的改进方案:
		把slot的概念取消。抽象了一个新的概念叫做容器Container
		以前的slot有map和redcue之分,但是现在的Container没有。
	
	第二个问题的解决方案:
		MRAppMaster其实也是一个JVM进程。  在新版本中。这个MRAppmaster其实就是运行在一个Container中
		MRAppMaster主控程序,随便找一个节点都可以运行
		只要被分配了Container容器即可
		HA 高可用
		MRAppMaster  是  mapTask和reduceTask的主节点


	
	交互式编程




	对应关系:
	一个container  -----  一个 JVM进程  ---- 一个 Task 任务
	container 抽象的逻辑资源单位   盒子
	Docker  最核心的概念: container
	Connecting to ResourceManager ......
	hadoop jar 




	
YARN集群的调度器有三种策略;
1、FIFO  : 先进先出 所有的任务都是串行执行。 每个任务执行的之后,占用整个集群的资源
2、Fair Scheduler  : 公平调度器
3、Capacity Scheduler    可配置的容量调度器





**/		   


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值