目录
第七章、 MapReduce
摩尔定律:CPU性能大约每隔18个月翻一番,从2005年逐渐失效
分布式并行编程:当人们不再把性能寄托在CPU提升上,人们开始借助分布式并行编程来提高程序的性能。分布式并行程序运行在大规模计算机集群上,充分利用集群的并行处理能力。
一、 MapReduce概述
1.1 MapReduce与传统并行计算框架的区别
传统并行计算框架 | MapReduce | |
集群架构/容错性 | HPC【高性能计算集群】,共享式(共享内存/共享存储),容错性差 | 非共享式,容错性好 |
硬件/价格/扩展性 | 刀片服务器、高速网、SAN,价格贵,扩展性差 | 普通PC机,便宜,扩展性好 |
编程/学习难度 | what-how,难 | what,简单 |
适用场景 | 实时、细粒度计算、计算密集型 | 批处理、非实时、数据密集型 |
1.2 MapReduce思想
MapReduce将复杂的、运行于大规模集群上的并行计算过程高度地抽象到了两个函数:Map和Reduce
设计思想:“计算向数据靠拢”,而不是“数据向计算靠拢”,因为移动数据需要大量的网络传输开销,移动计算更经济安全
MapReduce模型:一个存储在分布式文件系统的大规模数据集会被切分成许多独立的小数据集,这些小数据集被多个Map任务并行处理。MapReduce框架会为每个Map任务输入一个小数据集(分片),Map任务的结果将会作为Reduce任务的输入,最终由Reduce任务输出结果并写入分布式文件系统
适用MapReduce处理的前提:待处理的数据集可以分解成许多小数据集,而且每一个小数据集都可以并行处理
1.3 Map和Reduce函数
两个函数都是输入一个键值对,以一定的映射规则输出另一个键值对
函数 | 输入 | 输出 | 说明 |
Map | 输入来自分布式文件系统的文件块,这些文件块的格式是任意的,同一个元素不能跨文件块存储。 【键没有唯一性】 <k1,v1>如: <行号,“a b c”> | 【同一输入元素可以产生多个相同键的多个键值对】 List(<k2,v2>)如:<“a”,1>、<“b”,1> | 1.将小数据集进一步解析成一批<key,value>对,输入Map函数中进行处理 2.每个输入的<k1,v1>会输出一批<k2,v2>。<k2,v2>是计算的中间结果 |
Reduce | <k2,List(v2)> 如:<“a”,<1,1,1>> | <k3,v3> <“a”,3> | Reduce的任务是将输入的一些列具有相同键的键值对以某种方式组合,然后产生输出,输出结果会合并成一个文件 输入的中间结果<k2,List(v2)>中的List(v2)表示是一批属于同一个k2的value |
二、 MapReduce的工作流程
2.1 概述
1、对输入进行检验、切分【逻辑切分】,通过RecordReader对切分产生的InputSplit进行处理,加载数据并转换成适合Map任务读取的键值对
2、将一个大的MapReduce作业拆分成多个Map任务,每个Map任务间是并行系,相互之间并不进行数据传输【不需要额外的数据传输开销】,且每个Map任务在存储相应数据的数据节点上执行【运算向数据靠拢,输入文件在本地】
3、当Map任务结束后,根据用户定义的映射规则生成键值对形式的多个中间结果【中间结果保存在本地】
4、当所有的Map任务结束后,中间结果进行一定的分区、排序、合并、归并等操作,得到<key,<value1,value2>>格式的键值对,这个从无序<key,value>得到有序<key,valuelist>的过程称为Shuffle【每个Map任务都会进行Shuffle】
5、Shuffle后的中间结果将被分发到多个Reduce任务【通过MapReduce框架自身实现】,在多台机器上运行【具有相同key的键值对会被发送到同一个Reduce任务中】,Reduce任务间是并行关系
6、Reduce任务根据用户定义的逻辑对中间结果进行汇总处理,并将得到的最终结果输出到分布式文件系统的相应目录中
P.S.
Split:HDFS 以固定大小的block 为基本单位存储数据,而MapReduce 的处理单位是split。split 是一个逻辑概念,它只包含一些元数据信息,比如数据起始位置、数据长度、数据所在节点等。它的划分方法完全由用户自己决定。
Map任务的数量:Hadoop为每个split创建一个Map任务,split 的多少决定了Map任务的数目。大多数情况下,理想的分片大小是一个HDFS块
Reduce任务的数量:最优的Reduce任务个数取决于集群中可用的reduce任务槽(slot)的数目,通常设置比reduce任务槽数目稍微小一些的Reduce任务个数(这样可以预留一些系统资源处理可能发生的错误)
2.2 Shuffle过程
shuffle:对Map任务的输出结果进行分区、排序、合并、归并等处理并交给Reduce的过程
该过程分为Map端操作和Reduce端操作
Map端的Shuffle过程
1、数据输入:输入数据。根据映射规则产生多个键值对
2、执行Map任务:每个Map任务会分配一个缓存,Map输出的键值对会被写入缓存,积累一定数量后再一次性写入【有利于降低寻址开销,方便I/O操作】
3、溢写(分区、排序和合并):当缓存中数据快占满内存时,启动溢写操作【启动后台另一个线程】
分区:将缓存中的数据进行按键值对分区【默认】,采用哈希函数对key处理,再对Reduce任务数量取模【hash(key) mod R】,用于将结果分配给相应Reduce任务
排序:根据key对数据进行内存排序【默认】。
Combiner合并操作【可选】:可以将key相同的value求和,得到<key,value1+value2>【合并不能改变最终结果】,文件归并时,如果溢写文件数量大于预定值(默认3)则可以再次启动Combiner,少于3不需要
4、写入磁盘:最后将数据写入磁盘,并清空缓存。每次溢写会产生新的溢写文件。
5、文件归并:在所有Map任务结束时,系统对所有溢写文件进行归并,生成一个大文件,这个大文件是分区的
6、通知Reduce:JobTracker会一直监测Map任务的执行,并通知Reduce任务来领取数据
Reduce端的Shuffle过程
1、领取数据:将保存在Map机器本地磁盘上的中间结果领取并放到Reduce任务自己机器的本地磁盘上,在Reduce任务开始前,一直在领取属于自己处理分区的数据。
2、归并数据:从Map端领取的数据会被放到Reduce任务的缓存上,同样的进行溢写操作,溢写过程中会将同key数据归并,然后排序,也可以使用用户定义的Combiner进行合并。当所有数据都领取完时,会生成一个大溢写文件。【每轮归并溢写文件的数量是设置的值,默认为10,即50个溢写文件需要5轮归并操作,得到5个大溢写文件】
3、数据输入给Reduce:若干得到的溢写大文件不会继续归并,可以直接输入Reduce任务中,减少磁盘读写开销。
三、 MapReduce 编程
MapReduce 编程框架分为三个部分
MapReduce 编程框架分为三个部分,分别创建如下三个类 public class WcMap extends Mapper<LongWritable, Text, Text, LongWritable>{ //重写map这个方法 //mapreduce框架每读一行数据就调用一次该方法 protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //具体业务逻辑就写在这个方法体中,而且我们业务要处理的数据已经被框架传递进来,在方法的参数中key-value //key是这一行数据的起始偏移量,value是这一行的文本内容 } } public class WcReduce extends Reducer<Text, LongWritable, Text, LongWritable>{ //继承Reducer之后重写reduce方法 //第一个参数是key,第二个参数是集合。 //框架在map处理完成之后,将所有key-value对缓存起来,进行分组,然后传递一个组<key,valus{}>,调用一次reduce方法 protected void reduce(Text key, Iterable<LongWritable> values,Context context) throws IOException, InterruptedException { } } public class WcRunner { public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException { //创建配置文件 Configuration conf = new Configuration(); //获取一个作业 Job job = Job.getInstance(conf); //设置整个job所用的那些类在哪个jar包 job.setJarByClass(WcRunner.class); //本job使用的mapper和reducer的类 job.setMapperClass(WcMap.class); job.setReducerClass(WcReduce.class); //指定reduce的输出数据key-value类型 job.setOutputKeyClass(Text.class); job.setOutputValueClass(LongWritable.class); //指定mapper的输出数据key-value类型 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(LongWritable.class); //指定要处理的输入数据存放路径 FileInputFormat.setInputPaths(job, new Path("hdfs://master:9000/user/cg/input")); //指定处理结果的输出数据存放路径 FileOutputFormat.setOutputPath(job, new Path("hdfs://master:9000/user/cg/output")); //将job提交给集群运行 job.waitForCompletion(true); } }