MR擅长处理离线的文本文件。MR+yarn将简单的运算逻辑很方便的拓展到海量数据背景下运行分布式程序逻辑。
如果自己来写分布式程序的话,那么首先会面临一个问题,如何将我的计算逻辑分发到其他节点上?如何汇总所有节点上的运行结果?
MR的总体框架:将运算逻辑分为两个阶段,map阶段和reduce阶段。map常见做些局部数据预处理,reduce常见做全局数据的汇总工作。每从文件中读取一行,就会发送给Map逻辑程序并运行一次。
MapReduce编程规范
需要定义两个类,一个是继承了mapper类的myMapper,另一个是继承了Reducer类的myReducer类。Mapper和Reducer类都是是一个泛型类,包含四个泛型参数:输入key的类型,输入value类型,输出的key的类型,输出value的类型。
map和reduce的数据输入输出都是以key-value对的形式进行分装的。默认情况下,框架传递给Mapper类的输入数据的key是处理的文本文件中一行的起始偏移量。hadoop实现了自己的序列化类型,所以这些key和value 的类型都应该写成hadoop的序列化类型:long->LongWritable,String->Text。
Mapper类的具体业务逻辑就写在map方法中,可以默认当执行到map方法时候,需要的数据已经以key-value的形式被框架传递进来了,key是处理的文本文件中一行的起始偏移量。
为了这个mapreduce的job还需要一个Runner类,用来描述一个特定的作业:那个是Mapper类,哪个是Reducer类,作业的输入数据的路径,作业的结果输出路径在哪里。主要的流程是就是:0.设置job所使用的类的在哪个jar包中,1.获取一个job类对象,2.然后设置本job使用的eMapper和Reducer类,3.设置map和reducer的输出的key-value的输出类型,4.设置原始数据的位置(输入数据的所在目录)和输出的结果数据的存放位置,5将job提交给集群运行。
运行的命令:hadoop jar <jar包的名字> <jar包中的job入口类>,此命令将jar包和关于此次任务的一些描述性信息形成的配置文件,提交给集群。
以下是一个wordcount的mapReduce程序需要的各个类:
- mapper类
package cn.itcast.hadoop.mr.wordcount;
import java.io.IOException;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
//4个泛型中,前两个是指定mapper输入数据的类型,KEYIN是输入的key的类型,VALUEIN是输入的value的类型
//map 和 reduce 的数据输入输出都是以 key-value对的形式封装的
//默认情况下,框架传递给我们的mapper的输入数据中,key是要处理的文本中一行的起始偏移量,这一行的内容作为value
public class WCMapper extends Mapper<LongWritable, Text, Text, LongWritable>{
//mapreduce框架每读一行数据就调用一次该方法
@Override
protected void map(LongWritable key, Text value,Context context)
throws IOException, InterruptedException {
//具体业务逻辑就写在这个方法体中,而且我们业务要处理的数据已经被框架传递进来,在方法的参数中 key-value
//key 是这一行数据的起始偏移量 value 是这一行的文本内容
//将这一行的内容转换成string类型
String line = value.toString();
//对这一行的文本按特定分隔符切分
String[] words = StringUtils.split(line, " ");
//遍历这个单词数组输出为kv形式 k:单词 v : 1
for(String word : words){
context.write(new Text(word), new LongWritable(1));
}
}
}
- reducer类
package cn.itcast.hadoop.mr.wordcount;
import java.io.IOException;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
public class WCReducer extends Reducer<Text, LongWritable, Text, LongWritable>{
//框架在map处理完成之后,将所有kv对缓存起来,进行分组,然后传递一个组<key,valus{}>,调用一次reduce方法
//<hello,{1,1,1,1,1,1.....}>
@Override
protected void reduce(Text key, Iterable<LongWritable> values,Context context)
throws IOException, InterruptedException {
long count = 0;
//遍历value的list,进行累加求和
for(LongWritable value:values){
count += value.get();
}
//输出这一个单词的统计结果
context.write(key, new LongWritable(count));
}
}
- runner类
package cn.itcast.hadoop.mr.wordcount;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
/**
* 用来描述一个特定的作业
* 比如,该作业使用哪个类作为逻辑处理中的map,哪个作为reduce
* 还可以指定该作业要处理的数据所在的路径
* 还可以指定改作业输出的结果放到哪个路径
* ....
* @author duanhaitao@itcast.cn
*
*/
public class WCRunner {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job wcjob = Job.getInstance(conf);
//设置整个job所用的那些类在哪个jar包
wcjob.setJarByClass(WCRunner.class);
//本job使用的mapper和reducer的类
wcjob.setMapperClass(WCMapper.class);
wcjob.setReducerClass(WCReducer.class);
//指定reduce的输出数据kv类型
wcjob.setOutputKeyClass(Text.class);
wcjob.setOutputValueClass(LongWritable.class);
//指定mapper的输出数据kv类型
wcjob.setMapOutputKeyClass(Text.class);
wcjob.setMapOutputValueClass(LongWritable.class);
//指定要处理的输入数据存放路径
FileInputFormat.setInputPaths(wcjob, new Path("hdfs://weekend110:9000/wc/srcdata/"));
//指定处理结果的输出数据存放路径
FileOutputFormat.setOutputPath(wcjob, new Path("hdfs://weekend110:9000/wc/output3/"));
//将job提交给集群运行
wcjob.waitForCompletion(true);
}
}
mr程序的提交运行模式
- 本地模式运行
- 在windows的eclipse里面直接运行main方法,就会将job提交给本地执行器localjobrunner执行。输入输出数据可以放在本地路径下(c:/wc/srcdata/)或者输入输出数据也可以放在hdfs中(hdfs://weekend110:9000/wc/srcdata)
- 在linux的eclipse里面直接运行main方法,但是不要添加yarn相关的配置,也会提交给localjobrunner执行。输入输出数据可以放在本地路径下(c:/wc/srcdata/)或者输入输出数据也可以放在hdfs中(hdfs://weekend110:9000/wc/srcdata)
- 集群模式运行
- 1/将工程打成jar包,上传到服务器,然后用hadoop命令提交 hadoop jar wc.jar cn.itcast.hadoop.mr.wordcount.WCRunner
- 2/ 在linux的eclipse中直接运行main方法,也可以提交到集群中去运行,但是,必须采取以下措施:1.在工程src目录下加入 mapred-site.xml 和 yarn-site.xml 2.将工程打成jar包(wc.jar)放在classpath中,同时在main方法中添加一个conf的配置参数conf.set("mapreduce.job.jar","wc.jar");
- 3/在windows的eclipse中直接运行main方法,也可以提交给集群中运行,但是因为平台不兼容,需要做很多的设置修改:1/要在windows中存放一份hadoop的安装包(解压好的)。2/要将其中的lib和bin目录替换成根据你的windows版本重新编译出的文件。3/再要配置系统环境变量 HADOOP_HOME 和 PATH。4/修改YarnRunner这个类的源码
mr的本地模式运行说明MR是与底层的文件系统解耦合的。通过fs实例来读文件,不管fs到底是什么文件系统。但是为了程序效率程序更高,应该尽可能将运算逻辑和数据放在一个相同的节点上。
问题:什么情况下本地运行呢?什么时候是集群运行模式呢?答案:Runjar根据配置文件中的mapreduce.framework.name选项类获取一个代理对象。如果代理对象是与yarn集群进行通信的,那么就会以集群模式运行,如果是一个与本地jvm进行通信的的代理对象,那么就以本地模式运行。所以在IDE中将configuration的各个配置项设置好或者在本地的classpath下编写mr和yarn的配置文件,然后将工程打成一个jar包放在classpath下,然后通过conf设置好job所在的jar类,那么在IDE中也能以集群模式运行。
mapreduce任务提交运行的机制原理
yarn集群有两种角色节点:resourceManager和nodeManager。 MapReduce框架运行时会有两种角色的进程:MRappMaster进程和yarn child进程
提交运行MR程序的主要过程如下:
- 首选时yarn集群分配资源过程:
当runner类执行到job.waitforcompletion()方法的时候,就会产生一个进程,叫做RunJar,这个进程就相当于一个mr程序的提交客户端。Runjar启动之后:1.想resourcemanager申请执行一个job,2.然后resourcemanager返回job相关资源的提交的路径(叫作staging -dir)和为本job产生的jobid给RunJar,3.Runjar把自己运行过程中会用到资源提交到staging-dir中,4向resourcemanager汇报提交的结果,5将当前这个job加入到job队列中并分配好在那些nodemanager运行,6.nodemanager通过心跳来领取resourcemanager上需要自己执行的job,7.nodemanager产生一个资源容器,用于运行这个job,并且将staging-dir的job相关资源文件拉取到容器中。
- 接下来启动进程的操作就交给mapreduce框架:
8.resourcemanager在某个nodemanager上启动一个MRappMaster进程,9.MRappMaster启动之后会向resoucemanager注册自己的相关信息:我在哪个NM上,我的id,我的标识等。并且询问RM分配给关于本次job的资源都在那里。10去被分配启动任务的NM上,启动map进程(yarn child)并监控所有的map任务的执行状态。11.map任务都执行完了之后,在某个NM上启动reducer进程,并监控reduce任务的进度。12.所有的reduce执行完了之后,MRappMaster向RM申请注销自己。
Yarn的通用性意义
无论上层运行什么样的计算框架都能运行在yarn框架。yarn集群只负责分配资源,划分运算节点,之后只要再把你的计算框架的母进程(需要实现AppMaster接口或者抽象类)启动起来之后,再把yarn集群分配的资源的位置等信息告诉给母进程,之后我就不管了,你运算框架自己的进程由你的母进程负责去启动。
分布式缓存
job提交流程源码的跟踪
具体的提交操作是在submit方法中,connect负责给job的一个client进行赋值。客户端的执行语句:job.waitforcompletion()会产生一个runjar进程。主要的流程是waitforcompletion()主要有意义的语句就是一个submit()方法的调用。submit()方法首先会执行connect()方法,connect方法的主要工作就是通过新建对象给job的cluster成员进行赋值,cluster中持有一个成员叫做client,这个成员会在cluster的构造方法的initialize方法中被赋值,这个client就是和执行器进行通信的一个客户端。执行器可以是本地执行器,也可以是yarn集群的执行器。client构造为和哪种执行器进行通信,决定了mr程序到底是本地执行还是集群执行。在initialize()方法中会读取配置对象configuration中“mapreduce.framework.name"对应的值,如果值是yarn,那么就会将client构造为与yarn进行通信的rpc代理类。如果没找到或者值不是yarn,那就构造一个与本地执行器进行通信的rpc代理类赋值给client。
获得了cluster对象之后,会利用client与执行器尽心交流,获得一个jobid和一个stagingdir,并将job执行依赖的一些文件提交到stagingdir中。
来源:oschina
链接:https://my.oschina.net/u/4489002/blog/3217068