Map Reduce(计算框架)
Map Reduce是Hadoop提供的一款通用的并行计算框架,该计算框架可以计算来自于文本文件、NoSQL、RDBMS系统中的数据。该计算实质是利用了HDFS集群中的DataNode所在机器的CPU、内存和少许磁盘完成分布式计算。
该计算分为两个阶段:
①Map reduce将一个大任务拆分若干个小任务(数据拆分),Map阶段作用是对每一小任务对应的数据做归类分析(分区、排序、Combiner Map Shuffle),并且将归类分析的结果存储到本地磁盘(不是HDFS),该过程称为MapTask,在该过程中系统会根据任务划分在各个物理节点上同时启动多个MapTask完成第一阶段归类分析,加速归类分析的过程。
②Map Reduce在完成Map阶段后,会根据系统设置的汇总并行度数开启多个ReduceTask对第一阶段计算的临时结果进行归并汇总,我们把该阶段称为ReduceTask,在该阶段所有的汇总节点会将所以Map任务计算的中间结果下载到本地(不是HDFS),在本地执行归并排序(合并、排序 Reduce Shuffle)最终将结果输出到HDFS、NoSQL、RDBMS中。
组件说明:
NodeManager
每个节点上装有一个NM,主要的职责有:
(1)为应用程序启动容器,同时确保申请的容器使用的资源不会超过节点上的总资源。
(2)为task构建容器环境,包括二进制可执行文件,jars等。
(3)为所在的节点提供了一个管理本地存储资源的简单服务,应用程序可以继续使用本地存储资源即使它没有从RM那申请。比如:MapReduce可以使用该服务程序存储map task的中间输出结果。
调度器
调度器收集所有正在运行的应用程序的资源请求并构建一个全局规划进行资源分配。调度器会根据应用程序相关的约束(如合适的机器)和全局约束(如队列资源总量,用户可提交作业总数等)分配资源。
调度器使用与容量调度类似的概念,采用容量保证作为基本的策略在多个应用程序间分配资源。
调度器的调度策略如下:
· 选择系统中“服务最低”的队列(如何定义服务最低?可以是资源利用量最低的队列,即:已使用的资源与总共可用资源比值最小)
· 从该队列中选择优先级最高的作业
· 尽量满足该作业的资源请求
ASM
ASM负责管理系统中所有应用程序的AM,ASM负责启动AM,监控AM的运行状态,在AM失败时对其进行重启等。
为了完成该功能,ASM主要有以下几个组件:
(1) SchedulerNegotiator:与调度器协商容器资源,并返回给AM
(2) AMContainerManager:告知NM,启动或者停止某个AM的容器
(3) AMMonitor:查看AM是否活着,并在必要的时候重启AM
ApplicationMaster
每个应用程序均会有一个AM,主要职责有:
(1) 与调度器协商资源
(2) 与NM合作,在合适的容器中运行对应的task,并监控这些task执行
(3) 如果container出现故障,AM会重新向调度器申请资源
(4) 计算应用程序所需的资源量,并转化成调度器可识别的格式(协议)
(5) AM出现故障后,ASM会重启它,而由AM自己从之前保存的应用程序执行状态中恢复应用程序。
调度器API
Yarn调度器与AM之间仅有一个API:
Response allocate(List ask, List release);
AM使用一个ResourceRequest列表请求特定资源,并同时可要求释放一些调度器已经分配的容器。
Response包含三方面内容:新分配的容器列表,自从上次AM与RM交互以来已经计算完成的容器的状态(包含该容器中运行task的详细信息),当前集群中剩余资源量。 AM收集完成容器的信息并对失败的任务作出反应。资源剩余量可用于AM调整接下来的资源请求,如MapReduce AM可使用该信息以合理调度maps和reduces从而防止产生死锁。
第 1 步:Client执行main()函数中run job(),开启作业
通过submit或者waitForCompletion提交作业,waitForCompletion()方法通过每秒循环轮转作业进度,如果发现与上次报告有改变,则将进度报告发送到控制台。其实waitForComplection()方法中还是调用submit()方法。
第 2 步:client向RM发送作业请求同时RM将作业id(在YARN中叫做应用程序ID)以及jar包存放路径返回给Client。
客户端向ResourceManager提交请求GetNewApplicationRequest,ResourceManager为其返回应答GetNewApplicationResponse,该数据结构中包含多种信息,包括ApplicationId、可资源使用上限和下限等。
第 3 步:这时候作业客户端检查输出说明、计算输入分片(可以通过yarn.app.mapreduce.am.computer-splits-in-cluster在集群上产生分片)。Client会把Jar路径为前缀作业id为后缀作为唯一存放路径,并将作业信息(jar、配置文件、分片信息)写入到HDFS集群中,默认情况下jar包写10份,而其他数据只写3份,当该程序运行完后删除这些数据
第 4 步:客户端Client将启动ApplicationMaster所需的所有信息(包括描述更为详细的Jar存放地址)打包到数据结构ApplicationSubmissionContext中,然后调用submitApplication(ApplicationSubmissionContext)将ApplicationMaster提交到ResourceManager上。
第 5 步(5a-5b):资源管理器RM在收到submitApplication()消息后,将其放入调度器(Scheduler),调度器为其分配一个容器Container,向NM发送命令,然后NM在RM的管理下在container中启动MRAPPMaster进程
ApplicationMaster首先需向ResourceManager发送注册请求RegisterApplicationMasterRequest,而ResourceManager将返回RegisterApplicationMasterResponse。
第 6 步:applicationmaster对作业进行初始化,创建过个薄记对象以跟踪作业进度。MR根据HDFS中jar包数据量为NM分配任务.
第 7 步:applicationmaster接受来自HDFS在客户端计算的输入分片,对每一个分片创建一个map任务,任务对象,由mapreduce.job.reduces属性设置reduce个数。
uber模式
当任务小的时候就会启动一个JVM运行MapReduce作业,这在MapReduce1中是不允许的,这样的作业在YARN中成为uber作业,通过设置mapreduce.job.ubertask.enable设置为false使用。
那什么是小任务呢?
当小于10个mapper且只有1个reducer且输入大小小于一个HDFS块的任务。
但是这三个值可以重新设定:mapreduce.job.ubertask.maxmaps
第 8 步:如果作业不适合uber任务运行,applicationmaster就会为所有的map任务和reduce任务向资源管理器申请容器(Container,仅包含内存和cpu两类资源)。
ApplicationMaster使用ResourceRequest类描述每个Container。一旦为任务构造了Container后,ApplicationMaster会向ResourceManager发送一个AllocateRequest对象,以请求分配这些Container。ResourceManager会为ApplicationMaster返回一个AllocateResponse对象。
请求作业为任务指定内存需求,map任务和reduce任务的默认都会申请1024MB的内存,这个值可以通过mapreduce.map.memory.mb和mapreduce.reduce.memory.mb来设置。
这里的内存分配策略和mapreduce1中不同,在MR1中tasktracker中有固定数量的槽,每个任务运行在一个槽(slot)中,槽有最大内存分配限制,这样集群是固定的,当任务使用较少内存时,无法充分使用槽的内存,造成其他任务不能够获取足够内存因而导致作业失败。
在YARN中,资源分为更细的粒度,所以避免了以上的问题。应用程序可以申请最小到最大内存限制的任意最小值的倍数的内存容量。默认值是1024~10240,可以通过yarn.scheduler.capacity.minimum-allocation-mb和yarn.scheduler.capacity.maximum-allocation.mb设定。任务可以通过设置mapreduce.map.memory.mb和mapreduce.reduce.memory.mb来请求1GB到10GB的任意1GB的整数倍的内存容量。
第 9 步(9a~9b):资源管理器为任务分配了容器,当ApplicationMaster(从ResourceManager端)收到新分配的Container列表后,会向对应的NodeManager发送ContainerLaunchContext以启动Container。NM会开启内部YARNChild,由YarnChild的java应用程序执行。
第 10 步:运行任务之前,首先将资源本地化,包括作业配置、jar文件和所有来自分布式缓存的文件。YARNChild根据命令到HDFS检索作业资源。
第 11 步:YARNChild开启MapTask 或者Reduce Task
ApplicationMaster会不断重复步骤8~9,直到所有任务运行成功。
Map Reduce 环境搭建 (单机)
① 修改etc/hadoop/mapred-site.xml(文件默认不存在)
[root@CentOS ~]# cp /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml.template /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml
[root@CentOS ~]# vim /usr/hadoop-2.6.0/etc/hadoop/mapred-site.xml
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
② 修改etc/hadoop/yarn-site.xml
[root@CentOS ~]# vim /usr/hadoop-2.6.0/etc/hadoop/yarn-site.xml
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<property>
<name>yarn.resourcemanager.hostname</name>
<value>CentOS</value>
</property>
③ 启动Yarn(关闭stop-yarn.sh)
[root@CentOS ~]# start-yarn.sh
④ 打开浏览器访问ResourceManager
MapReduce编程模型【重点】
maven依赖
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.6</version>
<scope>system</scope>
<systemPath>C:/Java/jdk1.8.0_144/lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs </artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-jobclient</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-common</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-shuffle</artifactId>
<version>2.6.0</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
案例:统计一个文件中每个词出现的次数
Mapper端 -归类分析
static class WordsMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] tokens = value.toString().split(" ");
for (String token : tokens) {
context.write(new Text(token), new IntWritable(1));
}
}
}
Reducer端-归并汇总
static class WordsReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int total = 0;
for (IntWritable value : values) {
total += value.get();
}
context.write(new Text(key), new IntWritable(total));
}
}
完整代码
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import java.io.IOException;
public class TestWords {
public static void main(String[] args) throws Exception {
//1.创建job
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
//2.设置数据的格式类型 决定读入和写出的数据的方式
job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class);
//3.设置数据的读入和写入路径
Path src = new Path("/demo/words");
Path res = new Path("/demo/res");
FileSystem fileSystem = FileSystem.get(conf);
if (fileSystem.exists(res)) {
fileSystem.delete(res, true);
}
TextInputFormat.addInputPath(job, src);
TextOutputFormat.setOutputPath(job, res);
//4.设置数据的处理规则
job.setMapperClass(WordsMapper.class);
job.setReducerClass(WordsReducer.class);
//5.设置Mapper和Reducer的key,value类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
//6.提交任务job
job.waitForCompletion(true);
}
static class WordsMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] tokens = value.toString().split(" ");
for (String token : tokens) {
context.write(new Text(token), new IntWritable(1));
}
}
}
static class WordsReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int total = 0;
for (IntWritable value : values) {
total += value.get();
}
context.write(new Text(key), new IntWritable(total));
}
}
}
参考博客 https://blog.csdn.net/sunspeedzy/article/details/69119521