MapReduce

MapReduce简介
MapReduce是hadoop四大组件之一(HDFS,MapReduce,YARN和Comment),是一种分布式计算编程模型,用于解决海量数据的计算问题。

MapReduce思想原理
MapReduce采用分而治之的思想,将大文件切割成片,然后由多个map task并行处理,处理完成后交由reduce再做合并,最后输出结果

MapReduce执行过程
这里我们以经典例子WordCount(词频分析)来进行说明

切割
首先将大文件切割成片(片的大小默认与block等同,由于中文使用utf8存储占3个字节,所以切割时具体大小可能会有所波动),这里我们假设切割成了两个split切片,开启两个map task和两个reduce task。

map
那么接下来,一个map task负责处理一个切片,首先从split切片中读取数据,设定按行读取,读出来为一个<K1,V1>对,这里的K1默认为偏移量,如下图,读出的数据即为<0,“hello world”> <13,“hello hadoop”>,注意每行有个换行符。

读出来的数据将进入map进行处理,在map处理中,数据会依照需求转换为新的<k2,v2>对,这里因为我们要做词频统计,所以将v1分割成单个单词,新的k2使用单词本身,而value定为1,表示其出现了一次,如下图,新的<k2,v2>即为<hello,1> <world,1><hello,1> <hadoop,1>。
在这里插入图片描述
到这里map处理就完成了,接下来是shuffle write,将数据写入到本地磁盘(而不是HDFS,因为这只是中间数据,使用完后会直接删除)

shuffle write - 分区
在map处理完成之后,我们先将数据以<k2,v2>对的形式写到一个100M的buffer(一个环形内存缓冲器,阈值0.8)中,在写入之前会先对其进行分区处理,分区默认由HashPartitioner来执行(根据实际job我们可以自定义分区器),规则是k2的hash值与reduce task的个数取模,hash(k2)%reduce.num,这样这些数据就会被分为reduce.num个分区。此时数据由3部分组成:分区号,<k2,v2>。

这里有2个reduce task,所以会有两个分区0分区和1分区,最后所有0分区的数据会送到同一个reduce task上进行处理,所有1分区的数据会送到另一个reduce task上处理。

shuffle write - 溢写
分区完成后写入buffer,随着数据不断写入,当写满80M时,这80M内存会被封锁,然后对其中数据进行排序,分组和combiner(小聚合)。

排序主要是按照分区号和key值进行。这里key值是字符串,所以按照ASII码排序,根据实际job需求我们可以自定义排序方式。排完序数据为<hadoop,1> <hello,1> <hello,1> <world,1>

分组指的是相同key的value放到一个集合中,分完组后数据为<hadoop,{1}> <hello,{1,1}> <world,{1}>

分组和排序都是为了提高后面reduce处理时的分组效率

combiner是可选的,默认不执行,因为并不是所有的job都适用combiner。combiner的作用是对map端的输出先做一次合并(迷你reduce),以减少在map和reduce节点之间的数据传输量,提高网络IO性能。combiner后的数据为<hadoop,1> <hello,2> <world,1>

以上步骤完成后将数据溢写到磁盘,完成期间数据写入剩余的20M中,防止阻塞。

shuffle write - 归并
从buffer溢写出来的文件皆是小文件,其内部已经按照分区号分好区且每个分区内数据有序,为了之后reduce方便处理,我们将所有小文件归并成一个大文件,归并时使用归并排序,确保大文件也是有序的
在这里插入图片描述

shuffle read
在reduce执行之前,是shuffle read阶段。

首先reduce task询问map任务是否已经完成,若完成,reduce task所在的节点去map端读取数据,即上面最终形成的大文件(这里可以看出map task和reduce task最好在同一节点上,这样可以将网络IO变成磁盘IO,效率更高)。因为上面的文件是分好区的,所以对应的reduce task只需读对应分区的数据即可,如reduce task0只读大文件中0号分区的数据。

将数据读取到内存中,同样内存达到阈值就会溢写,溢写前会做排序处理,这里按key值排序就可以了。溢写后形成一些小文件,当数据全部读取完成后,将小文件归并排序成一个大文件。
在这里插入图片描述

reduce
reduce的核心思想:将“相同”key值数据为一组调用一次reduce方法

reduce对大文件进行分组,可根据实际情况自定义分组条件,这里以相同key值的<k2,v2>为一组。因为前面所做的排序和分组,这里的分组就可以更快速简单的完成,然后按组读取文件,一组数据调用一次reduce方法,产生结果写入HDFS上的output文件中

===========================================================================
MapReduceV1
MapReduce采用 Master/Slave 架构,在hadoop1.x版本中,由全局唯一的 JobTracker(主)和多个 TaskTacker(从)组成。

JobTracker
JobTracker负责资源和任务的调度。
JobTracker会对集群中所有的TaskTracker与job进行监控,当有新的作业进入到集群中时,JobTracker中的调度器(scheduler)会根据资源的使用情况合理的分配这些作业。一旦TaskTracker挂掉,scheduler会将原来在这个TaskTracker上面执行的任务转移到其他的节点上面继续执行。

TaskTracker
TaskTracker 接收JobTracker 发送过来的命令并执行相应的操作,同时会周期性地向JobTracker发送“心跳”,汇报本节点上资源的使用情况和任务的运行进度。

执行流程
首先,客户端将MapRedecu应用程序打成jar包发送给JobTracker,请求执行该程序

JT拿到jar包后,向NameNode去请求MR应用程序所要计算的文件位置

JT拿到文件位置列表后,向有数据的TaskTracker节点发送消息,让其分配资源用于运行Map Task
该节点优先是DataNode节点,如果DN上的资源不够,就另选一个节点,但这会导致网络IO,降低效率

TT接收到命令后就开始分配资源

JT将Map Task分发到各个TT

TT开始执行任务,并周期性地向JobTracker发送“心跳”,汇报本节点上资源的使用情况和任务的运行进度

JT如果在10分钟内没有接收到来自TT的心跳,就会认为其已经死亡,将原来在这个TT上面执行的任务转移到其他的节点上面继续执行

当Map Task执行完成后,JT会为reduce task分配资源,然后监控reduce task的执行流程,最后执行完成输出
在资源充足的情况下,reduce task最好执行在与Map task相同的节点上,这样可以避免网络IO,提高效率
在这里插入图片描述
存在问题
在MapReduceV1中,JobTracker同时负责资源调度和任务调度,负载高,压力大,容易故障,而且只有一台,故障后整个集群都将不可用(单点故障)
同时JobTracker是MapRduce自带的强耦合的,如果spark要运行在这套框架上,则要自己去实现一个JT,这样集群内就有两个资源调度器,就会产生资源隔离和抢夺

MapReduceV2
针对MapReduceV1中的问题,在Hadoop2.x中,引入了YARN来进行资源管理。

YARN
YARN的基本思想是将JobTracker的两个主要功能(资源管理和作业调度/监控)分离,主要方法是创建一个全局的ResourceManager(RM)和若干个针对应用程序的ApplicationMaster(AM)。即RM只有一个,每个应用程序都会有自己的AM。RM负责资源调度,AM负责任务分配。

ResourceManager
ResourceManager(RM):RM是一个全局的资源管理器,负责整个系统的资源管理和分配。它主要由两个组件构成:调度器(Scheduler)和应用程序管理器(Applications Manager,ASM)

调度器仅根据各个应用程序的资源需求进行资源分配

应用程序管理器ASM负责管理整个系统中所有应用程序,包括应用程序提交、与调度器协商资源以启动ApplicationMaster、监控ApplicationMaster运行状态并在失败时重新启动它等

为解决单点故障,RM采用了类似hdfs namenode高可用性的设计,给RM引入了active和standby状态,不过没有与journalnode相对应的角色,只是由zookeeper来负责维护RM的状态

ApplicationMaster
ApplicationMaster是每个应用都有的,负责向调度器索要适当的资源容器,运行任务,跟踪应用程序的状态和监控它们的进程并向ASM汇报心跳,处理任务的失败原因,在任务运行失败时重新为任务申请资源以重启任务

spark要运行在yarn上实现对应的ApplicationMaster即可

NodeManager
MapReduce2.x中依然采用主从结构,ResourceManager为主节点,NodeManager(NM)为从节点。
NM负责启动RM分配的container,并且监视container的运行情况,向RM汇报本节点的各种信息,并且接受来自AM的作业分配信息
资源本地化:在启动container的时候,NM会设置一些必要的环境变量以及将container运行所需的jar包、文件等从hdfs下载到本地

Container
Container是YARN中的资源抽象,它封装了某个节点上的多维度资源,如内存、CPU、磁盘、网络等,当AM向RM申请资源时,RM为AM返回的资源便是用Container表示。YARN会为每个任务分配一个Container,且该任务只能使用该Container中描述的资源

执行流程
client向NameNode请求所要计算文件的block位置
client向ResourceManager申请资源
RM接收到客户端的申请后,随机找一个资源充足NodeManager节点,分配一个Container容器
NM在分配的Container容器中启动一个ApplicationMaster
client将从NN处获得的block位置信息交给AM
AM拿到block位置信息后,去向RM申请资源
RM在数据所在的节点上分配Container容器(如果节点资源不够,则分配到与节点同机架的一个随机节点上,如果同机架节点资源都不够,则随机选一个节点)
NM在container容器中启动一个yarn-child进程
AM将任务分发到各节点的Container容器的yarn-child进程中执行,执行过程与MapReduceV1相同(执行过程中AM 监视运行着的task直到完成,当task失败时,申请新的container运行失败的task)
task都完成后,AM退出

在这里插入图片描述

hadoop MapReduce on YARN–WordCount案例
YARN集群配置
关闭所有节点上的HDFS相关进程

  stop-dfs.sh

配置mapred-site.xml文件(cp mapred-site.xml.template mapred-site.xml

 <!-- MapReduce的架构体系,这里使用MapReduceV2,即YARN -->
  <property>
          <name>mapreduce.framework.name</name>
          <value>yarn</value>
  </property>

配置yarn-site.xml文件

<!-- NodeManager上运行的附属服务。需配置成mapreduce_shuffle,才可运行MapReduce程序 -->
 	<property>
           <name>yarn.nodemanager.aux-services</name>
            <value>mapreduce_shuffle</value>
    </property>
    <property>
            <name>yarn.resourcemanager.ha.enabled</name>
            <value>true</value>			<!-- 启用RM的高可用 -->
    </property>
    <property>
            <name>yarn.resourcemanager.cluster-id</name>
            <value>cluster1</value>				<!-- YARN对外提供的服务的id -->
    </property>
    <property>
            <name>yarn.resourcemanager.ha.rm-ids</name>
            <value>rm1,rm2</value>			<!-- 实现RM高可用的节点id -->
    </property>
    <property>
            <name>yarn.resourcemanager.hostname.rm1</name>
            <value>node01</value>				<!-- rm1对应的真实节点 -->
    </property>
    <property>
            <name>yarn.resourcemanager.hostname.rm2</name>
            <value>node02</value>			  <!-- rm2对应的真实节点 -->
    </property>

	<!--配置三台zookeeper的位置信息 -->
    <property>
           <name>yarn.resourcemanager.zk-address</name>
           <value>node02:2181,node03:2181,node04:2181</value>
    </property>

将配置完成的hadoop安装包发送到其他节点,覆盖之前的安装包,也可以先删除之前的安装包

  scp -r hadoop-2.6.5 root@node02:/opt/zgl/
  scp -r hadoop-2.6.5 root@node03:/opt/zgl
  scp -r hadoop-2.6.5 root@node04:/opt/zgl/

在node01 或 node02 上启动HDFS

 start-dfs.sh

在node01 或 node02 上启动YARN,这里我们在node01上启动

 satrt-yarn.sh

因为 HDFS 和 YARN 都是hadoop的组件之一,这里步骤6,7可以用 start-all.sh 代替

启动后使用jps查看,成功的话node01节点上会启动ResourceManager进程,node02,node03,node04上会启动NodeManager进程。

[root@node01 ~]# jps
2390 NameNode
2726 DFSZKFailoverController
3111 Jps
2839 ResourceManager
2584 JournalNode

[root@node02 ~]# jps
2834 Jps
2706 NodeManager
2487 JournalNode
2393 DataNode
2571 DFSZKFailoverController
2268 QuorumPeerMain
2333 NameNode

这里我们会发现Node02上并没有启动standby状态的RM,这是因为系统默认就是不启动的,这里我们可以手动将其启动

yarn-daemon.sh start resourcemanager

 [root@node02 ~]# jps
 3008 ResourceManager
 2706 NodeManager
 2487 JournalNode
 2393 DataNode
 2571 DFSZKFailoverController
 2268 QuorumPeerMain
 2333 NameNode
 3055 Jps

YARN也提供Web UI来显示job相关信息,浏览器输入node01:8088。后面我们运行WordCount时,就可以在这里查看job状态
在这里插入图片描述
eclipse配置
在这里插入图片描述
在下图对MapReduce进行配置(确保node02上已启动RM)
在这里插入图片描述
新建一个java项目,将hadoop安装包中share\hadoop目录下 common,hdfs,tools,yarn,mapreduce五个文件夹中的jar包及其下lib文件夹中的jar包整合导入项目并build path
将mapred-site.xml,yarn-site.xml文件拷贝到source文件夹中
在这里插入图片描述
接下来,我们就可以开始写代码了

WordCount
我们先来回顾一下MapReduce处理大数据集的过程
在这里插入图片描述
做一些准备工作
首先我们在本地创建两个文本文件file1.txt和file2.txt,使file1.txt内容为"Hello World\nBye World",file2.txt的内容为"Hello Hadoop\nBye Hadoop"。
在HDFS 上创建输入文件夹 input,上传本地文件到集群的input 目录下

再来分析一下WordCount的执行过程

将文件拆分成splits,由于测试用的文件较小,所以我们写两个文件,每个文件为一个split,并将文件按行分割形成<key,value>对。这一步由MapReduce框架自动完成,其中偏移量(即key值)包括了回车所占的字节数
在这里插入图片描述
将分割好的<key,value>对交给用户定义的map方法进行处理,生成新的<k2,v2>对
在这里插入图片描述
得到map方法输出的<key,value>对后,Mapper会将它们按照key值进行排序分组,并执行Combine,将key至相同value值累加,得到Mapper的最终输出结果
在这里插入图片描述
Reducer先对从Mapper接收的数据进行排序,再交由用户自定义的reduce方法进行处理,得到新的<k3,v3>对,并作为WordCount的输出结果
在这里插入图片描述
分析完执行过程,我们来看一下代码实现。
要编程实现MapReduce计算,只需要继承Mapper类实现其map()方法,继承Reduce类实现其reduce()方法,并在main()函数中对job进行设置

序列化
因为HDFS涉及到序列化的问题,Hadoop的基本数据类型都实现了一个Writable接口,而实现了这个接口的类型都支持序列化。在Mapper类和Reducer类中都使用Hadoop自带的基本数据类型
在这里插入图片描述

map()

/**
     * @param KEYIN
     *            →k1 表示每一行的起始位置(偏移量offset)	LongWritable
     * @param VALUEIN
     *            →v1 表示每一行的文本内容	Text
     * @param KEYOUT
     *            →k2 表示每一行中的每个单词	Text
     * @param VALUEOUT
     *            →v2 表示每一行中的每个单词的出现次数,固定值为1	IntWritable
     */
public class WCMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
	Text myKey = new Text();	//设置Text类型的对象,用来封装KEYOUT,即k2
	IntWritable myValue = new IntWritable(1);	//设置IntWritable类型的对象并固定其值为1,即v2
	
	@Override
	protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
		System.out.println(key+"=========="+value);	//输出<k1,v1>值,这里的key为offset
		
		//使用hadoop自带的工具类StringUtil进行分词(按空格进行切割),相当于value.toString().split(" ")
		String[] words = StringUtils.split(value.toString(), ' ');
		
		//遍历分完的词,即k2,产生<k2,v2>,使用context进行输出。context是个上下文对象
		for (String word : words) {
			myKey.set(word);
			context.write(myKey,myValue);
		}
	}
}

reduce()

/**
 * KEYIN     即K2     表示行中出现的单词 	Text
 * VALUEIN     即V2     表示出现的单词的次数 	IntWritable
 * KEYOUT     即K3     表示行中出现的不同单词	Text
 * VALUEOUT 即V3     表示行中出现的不同单词的总次数	IntWritable
*/
public class WCReduce extends Reducer<Text, IntWritable, Text, IntWritable> {
	@Override
	protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
		//因为reduce处理的是“相同”key的一组数据,这里的key是真的相同,所以只要遍历v2累加计数即可
		 int sum = 0;
		 for (IntWritable value : values) {
			sum += value.get();
		}
		//输出<k3,v3>
		context.write(key, new IntWritable(sum));
	}
}

main()

public class WC {
	public static void main(String[] args) throws IOException,ClassNotFoundException, InterruptedException {
	
		//读取MapReduce配置信息,包括HDFS。设置为true则从本地项目source文件夹中读取配置文件
		Configuration conf = new Configuration(true);
		
		//构建一个job并指定job名称		
		Job job = Job.getInstance(conf,"WordCount");
		
		//设置当前main函数所在类
		job.setJarByClass(WC.class);
		
		//设置本地jar包位置 (第三种运行模式使用,另两种不用配置。运行模式在下面介绍)
		job.setJar("d:/mapreduce/wc.jar");
		
		//设置输入路径	args[0]表示参数手动输入
		FileInputFormat.setInputPaths(job, args[0]);
		
		//设置输出路径	这里路径写死,先判断路径下是否有文件,有则删除
		Path outputPath = new Path("/output/wordcount");
		FileSystem fs = outputPath.getFileSystem(conf);	
		if(fs.exists(outputPath)){
			fs.delete(outputPath,true);
		}
		FileOutputFormat.setOutputPath(job, outputPath);
		
		//设置Map class
		job.setMapperClass(WCMapper.class);
		
		//设置map输出key、value的类型
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(IntWritable.class);
		
		//设置reduce class
		job.setReducerClass(WCReduce.class);

		//设置reduce输出key、value的类型
		 job.setOutputKeyClass(Text.class);
         job.setOutputValueClass(IntWritable.class);
		
		//设置reduce task的个数 
		job.setNumReduceTasks(2);
		
		//提交作业
		job.waitForCompletion(true);
	}
}

main函数中主要做了如下几件事:
一是构建job,指定main函数所在类,指定输入、输出目录;
二是指定自定义的Mapper类和Reducer类及其对应输入输出key,value的类型;
三是提交作业

接下来我们就可以运行代码,运行前我们先来看几种运行模式
运行模式
1、local(在本地的eclipse上启动多个线程来模拟map task,reduce task执行,并未启动集群,所以YARN的job web UI页面不会有任务显示) ,用于测试环境
修改mapred-site.xml 中的mapreduce.framework.name,设置为local

<property>
	<name>mapreduce.framework.name</name>
	<value>local</value>
</property>

2、提交到集群中运行,用于生产环境
在本地将代码打成jar包,提交到集群。在集群上执行hadoop jar + jar包路径 + main()所在类的全类名 + 参数运行

[root@node01 mapreduce]# ls
wc.jar
[root@node01 mapreduce]# hadoop jar  ./wc.jar  com.hpe.mr.wc.WC  /input/*

3、在本机上的eclipse中直接提交任务到集群中运行,这里我们使用这种方式
(1) 修改配置文件 mapred-site.xml

<property>
		<name>mapreduce.framework.name</name>
		<value>yarn</value>
	</property>
	<property>
		<name>mapreduce.app-submission.cross-platform</name>
		<value>true</value>			<!-- 跨平台提交开启 -->
	</property>

(2) 将本地Application打成jar包,放在window下某一个位置,这里放在D盘的mapreduce文件夹中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(4)点击Arguments,在Program arguments中填写参数,即我们在main()中写的args[0]的值,点击运行
在这里插入图片描述
(5)我们在console窗口可以看到我们在map中定义的<k1,v1>的输出

0==========hello hadoop
 13==========bye hadoop
 0==========hello world
 12==========bye world`

(6)在node01:8088页面可以看到任务状态
在这里插入图片描述
(7)在HDFS上查看output/wordcount
在这里插入图片描述
因为在main()中我们设置启动两个reduce task,所以最后输出两个文件part-r-00000和part-r-00001。
这里 bye world , world后面我不小心多加了个 ` ,所以最后结果是这样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值