MapReduce核心原理与使用

一MapReduce介绍

MapReduce是一种可用于数据处理的编程框架。MapReduce采用"分而治之"的思想,把对大规模数据集的操作,分发给一个主节点管理下的各个分节点共同完成,然后通过整合各个节点的中间结果,得到最终结果。简单地说,MapReduce就是"任务的分解与结果的汇总"。

在分布式计算中,MapReduce框架负责处理了并行编程中分布式存储、工作调度、负载均衡、容错均衡、容错处理以及网络通信等复杂问题,把处理过程高度抽象为两个函数:map和reduce,map负责把任务分解成多个任务,reduce负责把分解后多任务处理的结果汇总起来。

名字解释

MapReduce框架中的名词解释:

split:


分片是指MapReduce框架将数据源根据一定的规则将源数据分成若干个小数据的过程;其中,一个小数据集,也被称为一个分片。

Map:


Map有两层含义:

  • 其一、是指MapReduce框架中的Map过程,即将一个分片根据用户定义的Map逻辑处理后,经由MapReduce框架处理,形成输出结果,供后续Reduce过程使用;
  • 其二,是指用户定义Java程序实现Mapper类的map接口的用户自定义逻辑,此时通常被称为mapper。

Reduce:


Reduce也有两层含义:

  • 其一,是指MapReduce框架中的Reduce过程,即将Map的结果作为输入,根据用户定义的Reduce逻辑,将结果处理并汇总,输出最后的结果;
  • 其二,是指用户定义Java程序实现Reducer类的reduce接口的用户自定义逻辑,此时通常被称为reducer。

Combine:


Combine是一个可由用户自定的过程,类似于Map和Reduce,MapReduce框架会在Map和Reduce过程中间调用Combine逻辑通常Combine和reduce的用户代码是一样的(也可被称为本地的reduce过程),但是请注意并不是所有用MapReduce框架实现的算法都适合增加Combine过程(比如求平均值)。

Partition:分区器


在MapReduce框架中一个split对应一个map,一个partiton对应一个reduce(无partition指定时,由用户配置项指定,默认为1个)。 reduce的个数决定了输出文件的个数。比如,在需求中,数据是从对每个省汇总而成,要求计算结果按照省来存放,则需要根据源数据中的表明省的字段分区,用户自定义partition类,进行分区

二MapReduce框架中主要涉及到两个组件:

JobTrackerTaskTrackerHDFS中的组件是NameNodeDataNode),下面我们就分别看一下这两个组件。

TaskTracker

TaskTracker一个hadoop计算进程,运行在hadoop集群的datanode节点上。taskTracker的主要任务是运行JobTracker分配给它的实际计算任务,如运行MapReduce函数,当然也包括Shuffle过程。TaskTracker任务具体运行在一组slots上,slots的数量可以配置,一般slots的数量会配置成和这台机器的CPU核心数量一致。当TaskTracker收到JobTracker分配的一个task时,JobTracker会为这个task单独启动一个jvm进程,也就是说,每个mapreduce任务都会单独运行在一个jvm进程中(jvm也可以重用,这里不多做介绍)TaskTracker被分配的task数量决定于当前还有多少个空闲的slotsTaskTracker在运行task的过程中会向JobTracker发送心跳信息,发送心跳出了要告诉JobTracker自己是否存活外,心跳信息中还包含当前空闲的slots数量等信息。

JobTracker

JobTracker进程的作用是运行和监控MapReduceJob,当一个客户端向JobTracker提交任务时,过程如下图:

 

1. JobTracker接收Job请求

2. JobTracker根据Job的输入参数向NameNode请求包含这些文件数据块的DataNode节点列表

3. JobTracker确定Job的执行计划:确定执行此job的Map、Reduce的task数量,并且分配这些task到离数据块最近的节点上

4. JobTracker提交所有task到每个TaskTracker节点。TaskTracker会定时的向JobTracker发送心跳,若一定时间内没有收到心跳,JobTracker就认为这个TaskTracker节点失败,然后JobTracker就会把此节点上的task重新分配到其它节点上

5. 一旦所有的task执行完成,JobTracker会更新job状态为完成,若一定数量的task总数执行失败,这个job就会被标记为失败

6. JobTracker发送job运行状态信息给Client端


三 mapReduce执行流程

Mapper执行流程

每个Mapper任务是一个java进程,它会读取HDFS中的文件也可以读取本地文件系统,解析成很多的键值对,经过我们覆盖的map方法处理后,转换为很多的键值对再输出。整个Mapper任务的处理过程又可以分为以下几个阶段

Map任务的运行过程分为六个阶段。

第一阶段

是把输入文件按照一定的标准分片(InputSplit),每个输入片的大小是固定的。默认情况下,输入片(InputSplit)的大小与数据块(Block)的大小是相同的。如果数据块(Block)的大小是默认值64MB,输入文件有两个,一个是32MB,一个是72MB。那么小的文件是一个输入片,大文件会分为两个数据块,那么是两个输入片。一共产生三个输入片。每一个输入片由一个Mapper进程处理。这里的三个输入片,会有三个Mapper进程处理。

第二阶段

是对输入片中的记录按照一定的规则解析成键值对。有个默认规则是把每一行文本内容解析成键值对。是每一行的起始位置(单位是字节)是本行的文本内容。

第三阶段

是调用Mapper类中的map方法。第二阶段中解析出来的每一个键值对,调用一次map方法。如果有1000个键值对,就会调用1000map方法。每一次调用map方法会输出零个或者多个键值对。

第四阶段

是按照一定的规则对第三阶段输出的键值对进行分区(可以自定义分区个规则,自己可以写一个分区比较器)。比较是基于键进行的。比如我们的键表示省份(如北京、上海、山东等),那么就可以按照不同省份进行分区,同一个省份的键值对划分到一个区中。默认是只有一个区。分区的数量就是Reducer任务运行的数量。默认只有一个Reducer任务。

第五阶段

是对每个分区中的键值对进行排序。首先,按照键进行排序,对于键相同的键值对,按照值进行排序。比如三个键值对<2,2><1,3><2,1>,键和值分别是整数。那么排序后的结果是<1,3><2,1><2,2>。如果有第六阶段,那么进入第六阶段;如果没有,直接输出到本地的linux文件中。

第六阶段

是对数据进行归约处理,也就是reduce处理。键相等的键值对会调用一次reduce方法。经过这一阶段,数据量会减少。归约后的数据输出到本地的linxu文件中。本阶段默认是没有的,需要用户自己增加这一阶段的代码。

 

Reducer任务的执行过程详解

每个Reducer任务是一个java进程。Reducer任务接收Mapper任务的输出,归约处理后写入到HDFS中,可以分为如下图所示的几个阶段。

第一阶段

Reducer任务会主动从Mapper任务复制其输出的键值对。Mapper任务可能会有很多,因此Reducer会复制多个Mapper的输出。

第二阶段

是把复制到Reducer本地数据,全部进行合并,即把分散的数据合并成一个大的数据。再对合并后的数据排序。

第三阶段

是对排序后的键值对调用reduce方法。键相等的键值对调用一次reduce方法,每次调用会产生零个或者多个键值对。最后把这些输出的键值对写入到HDFS文件中。

四 MR程序的两种运行模式

分布式运行模式

必须在yarn平台上

核心特点:

整个运行流程由MRAppMaster控制;

每一个taskmaptaskreducetask)以及MRAppMaster,都是以独立的进程在nodemanager所提供的容器中执行;

 

本地运行模式:

在本地以单进程多线程方式运行;

核心特点:整个运行流程由LocalJobRunner(本地模拟器)控制,每一个taskmaptaskreducetask)都以线程方式执行

决定mr程序是以分布式还是以本地模型运行的关键点:

jobclient端的参数:  mapreduce.framework.name= local ? yarn ?

参数可以在jobclient的代码中设置;

也可以在jobclient所运行的机器的hadoop配置文件(mapred-site.xml)中配置;

注意:

如果以分布式模式运行mr,则你所访问的文件系统一定要是HDFS!!!(参数: fs.defaultFS = hdfs://....

如果以本地模式运行mr,则你所访问的文件系统可以是HDFS,也可以是本地文件系统;

本地模式运行可以在windows系统上(需要将windows本地库bin目录配置到环境变量PATH中),也可以在linux系统上;切记 windows本地库 而不是hadoop直接解压的哪个文件。

如果要在windows上运行jobclient,把mr job提交到YARN上分布式运行,则需要一下客户端参数:

Configuration conf = new Configuration(); // 加载classpath中的hadoop配置文件

// job api对象,在提交mrjob去运行时,有两种提交目的地选择:1.本地模拟器  2.yarn

conf.set("mapreduce.framework.name", "yarn");  //  mapred-site.xml

conf.set("fs.defaultFS", "hdfs://cts01:9000");  // core-site.xml

conf.set("yarn.resourcemanager.hostname", "cts01");

conf.set("mapreduce.app-submission.cross-platform", "true");  //跨平台提交;

 


五 如何实现
MR框架中的Writable接口

注意:自己写的类无论是key或是value都必须实现writable

接口中有两个核心方法:

write(DataOutput out)  :  序列化时将对象的属性数据变成二进制

readFields(DataInput in) : 反序列化时,将二进制变成数据属性并设置到对象中

注意:一个bean的序列化属性顺序要和反序列化一致

六 MR编程框架中的高级API

如果要用自定义类型Bean作为map输出的KEY,则需要以下编程接口:

Bean 必须实现  WritableComparable 接口:   write()  readFields()   compareTo()

分区器:

必须自定义一个分区器:

XPartitioner extends Partitioner<KEY,VALUE>

核心方法:getPartition(KEY k,VALUE v,int numReduceTasks)

public class MoviePartitioner extends Partitioner<MovieBean, NullWritable>{

public int getPartition(MovieBean key, NullWritable arg1, int reduceTaskNum) {

return key.getMovie().hashCode()&Integer.MAX_VALUE%reduceTaskNum;

}

 

}

return key.getMovie().hashCode()&Integer.MAX_VALUE%reduceTaskNum;

解释:因为有的对象的hashcode的值可能是负数,单与 &上整数的最大值(10000000.......,得到的是一个正数

分组比较器:必须自定义一个分组比较器

比如电影类的分组电影号分组

注意:自己写的分组比较器必须调用父类的构造方法

     public class MovieGroupCompartor extends WritableComparator{

 public MovieGroupCompartor() {

super(MovieBean.class,true);

}

@Override

public int compare(WritableComparable a, WritableComparable b) {

MovieBean key1 =(MovieBean)a;

MovieBean key2 =(MovieBean)b;

return key1.getMovie().compareTo(key2.getMovie());

}


自定义类当做key的mr程序执行过程


七 在map方法中如何获取maptask所处理的任务切片信息

 FileSplit inputSplit=(FileSplit)context.getInputSplit();

String filename = inputSplit.getPath().getName();

切片过程

由图可以看出切片个数和map个数一样

partition个数和reduce个数一样

 

 八 为什么要使用自己的类当做key?

因为如果不把自己的类当做key,那么对自己的类进行排序,只能放到集合中,也就是内存中,如果是海量的数据,对内存占用量太大,但是如果把自己的类当做key那么我们可以自定义排序规则,就可以让mapperT sak帮我排序

而且我们知道,mapperTask,会把自己得到的数据先放到内存缓冲区中排好序,分好组再放到本地磁盘中,这样每次最多使用一个block的大小的内存占用,(默认每一个mapper的任务量就是一个block的大小)。这样就解决了对内存占用大的问题。并且实现了对自己类的自定义排序。(可以看上面的自定义类当做key的mr程序执行过程图理解一下)。




猜你喜欢

转载自blog.csdn.net/aA518189/article/details/79931360
今日推荐