map-reduce基本概念和wordcount解析

 

1 map-reduce 简介

 

a) 是一个基础框架模型,后面学的框架都是对这个框架的包装,类比于 jdbc <----> mybatis/hibernate.

b) 是一种分布式计算模型,由Google提出,主要用于搜索领域,解决海量数据的计算问题.

c) MR由两个阶段组成:Map和Reduce,只需要实现map()和reduce()两个函数,即可实现分布式计算

d) 这两个函数的形参是key、value对,表示函数的输入信息。

 

核心在于<k1,v1> ---> <k2,v2> ---> <k3,v3> 的转变

 

 

2 map-reduce 执行过程

 


map-reduce基本概念和wordcount解析_第1张图片

 

 

 

 

2.0  对上图解释补充如下:

 

a) Reducer不一定只执行在一个TaskTracker上,举例如下:

   统计销售部门1,2,3 在2012,2013,2014年每个部门的绩效,

  那么假如在TaskTracker1上执行map得到部门1,2,3 2012年的绩效

  在TaskTracker2上执行map得到部门1,2,3 2013年的绩效

  在TaskTracker3上执行map得到部门1,2,3 2014年的绩效

   而reduce只针对某个部门执行所有年汇总,那么此时就要开启三个reduce,分别在三台TaskTracker上执行

b) Reducer放在哪个TaskTracker或者哪些个TaskTracker上执行是由JobTracker决定的

c) hdfs ----> 将数据传递给map  ----> reduce  ----> 将汇总结果写到hdfs,具体写在哪个datanode节点是由hdfs决定的。

d) map的输出是放在Linux磁盘上的, reduce获取的是每个map任务产生的数据,比如这个reduce是处理销售部门1的,那么就会专门获取销售部门1在所有map中产生的数据,而每个map任务仅仅是处理一部分数据,

map和reduce之间数据传递的过程叫做shuffle,shuffle属于reduce的第一阶段

e) reduce阶段是主动通过HTTP协议来获取map阶段产生的中间值,默认配置的路径可在mapred-default.xml/136行(<value>${hadoop.tmp.dir}/mapred/staging</value>运行时产生临时数据,运行完删除)

在执行map任务结束后,会通知jobtracker,然后jobtracker会决定在哪个/哪些个 tasktracker上启动reduce,

然后reduce会根据jobtracker记录的地址去Linux磁盘中获取map产生的中间值。

f) 要存储的数据被划分为100个block  可以简单理解为会启动100个map

map在输出的时候,会确定分成多少个区,如果分成3个区,那么就会对应三个reduce任务,即reduce的数量

是由map的分区决定的。

 

 

 

 

 

mapper: 5步

reduce:  3步

 

 

 1. map任务处理
a) 读取输入文件内容(从hdfs中读取 类比于文件类型有红豆 黄豆等类型),

解析成key、value对。对输入文件的每一行,解析成key、value对。每一个键值对调用一次map函数

map仅能处理一行数据。map中分组的作用就是让不同行的数据有见面的机会,从而为reduce的合并做准备(联想单词计数案例)

这个key是当前文本行的字节首地址, value是当前行的内容
b) 写自己的逻辑,对输入的key、value处理,转换成新的key、value输出。
c) 对输出的key、value进行分区(将不同类型的豆放在一起)。
d) 对不同分区的数据,按照key进行排序、分组。相同key的value放到一个集合中(分组仅仅是将map处理的中间结果具有相同key的value见面的机会,并不是作为减少网络开销的目的,其实也达不到减少网络开销的效果)
e) (可选)分组后的数据进行归约。

 

2 reduce


a) 对多个map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点。
b) 对多个map任务的输出进行合并、排序。写reduce函数自己的逻辑,对输入的key、value处理,转换成新的key、value输出, 得到最终结果 <k3,v3>
c) 把reduce的输出保存到文件中

 

下面贴出 wordcount的代码:

package mapreduce;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;

/**
 * 实现单词计数功能
 * 测试文件 hello内容为:
 * hello you
 * hello me
 * @author zm
 *
 */
public class MyWordCount {

	static String FILE_ROOT = "hdfs://master:9000/";
	static String FILE_INPUT = "hdfs://master:9000/hello";
	static String FILE_OUTPUT = "hdfs://master:9000/out";
	public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException, ClassNotFoundException {
		
		Configuration conf = new Configuration();
		FileSystem fileSystem = FileSystem.get(new URI(FILE_ROOT),conf);
		Path outpath = new Path(FILE_OUTPUT);
		if(fileSystem.exists(outpath)){
			fileSystem.delete(outpath, true);
		}
		
		// 0 定义干活的人
		Job job = new Job(conf);
		// 1.1 告诉干活的人 输入流位置     读取hdfs中的文件。每一行解析成一个<k,v>。每一个键值对调用一次map函数
		FileInputFormat.setInputPaths(job, FILE_INPUT);
		// 指定如何对输入文件进行格式化,把输入文件每一行解析成键值对
		job.setInputFormatClass(TextInputFormat.class);
		
		//1.2 指定自定义的map类
		job.setMapperClass(MyMapper.class);
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(LongWritable.class);
		
		//1.3 分区
		job.setNumReduceTasks(1);
		
		//1.4 TODO 排序、分组    目前按照默认方式执行
		//1.5 TODO 规约
		
		//2.2 指定自定义reduce类
		job.setReducerClass(MyReducer.class);
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(LongWritable.class);
		
		//2.3 指定写出到哪里
		FileOutputFormat.setOutputPath(job, outpath);
		job.setOutputFormatClass(TextOutputFormat.class);
		
		// 让干活的人干活
		job.waitForCompletion(true);
		
	}
	
}

/**
 * 继承mapper 覆盖map方法,hadoop有自己的参数类型
 * 读取hdfs中的文件。每一行解析成一个<k,v>。每一个键值对调用一次map函数,
 * 这样,对于文件hello而言,调用MyMapper方法map后得到结果:
 * <hello,1>,<you,1>,<hello,1>,<me,1>
 * 方法后,得到结果为: 
 * KEYIN,      行偏移量
 * VALUEIN,    行文本内容(当前行)
 * KEYOUT,     行中出现的单词
 * VALUEOUT    行中出现单词次数,这里固定写为1
 *
 */
class MyMapper extends Mapper<LongWritable, Text, Text, LongWritable>{

	@Override
	protected void map(LongWritable k1, Text v1, Context context)
			throws IOException, InterruptedException {
		
		String[] v1s = v1.toString().split(" ");
		for(String word : v1s){
			context.write(new Text(word), new LongWritable(1));
		}
	}
}

/**
 * <hello,{1,1}>,<me,{1}>,<you,{1}>, 每个分组调用一次 reduce方法
 * 
 * KEYIN,     行中出现单词
 * VALUEIN,   行中出现单词个数
 * KEYOUT,    文件中出现不同单词
 * VALUEOUT   文件中出现不同单词总个数
 */
class MyReducer extends Reducer<Text, LongWritable, Text, LongWritable>{

	protected void reduce(Text k2, Iterable<LongWritable> v2s, Context ctx)
			throws IOException, InterruptedException {
		long times = 0L;
		for(LongWritable l : v2s){
			times += l.get();
		}
		ctx.write(k2, new LongWritable(times));
	}
	
}

  结果如下:

[root@master ~]# hadoop fs -text /out/part-r-00000
Warning: $HADOOP_HOME is deprecated.

hello   2
me      3
you     1




测试文件内容:

hello	you
hello	me	me	me

 

 

输出结果如下:


[root@master hadoop]# hadoop fs -lsr /out
Warning: $HADOOP_HOME is deprecated.

-rw-r--r--   3 zm supergroup          0 2014-11-27 00:59 /out/_SUCCESS
-rw-r--r--   3 zm supergroup         26 2014-11-27 00:59 /out/part-r-00000


在linux中, _开头的文件常表示忽略不被处理的文件,
part-r-00000 中的 r表示reduce输出的结果,如果是map输出结果则为m,00000表示序号

 

 

 

 执行流程如下:


map-reduce基本概念和wordcount解析_第2张图片
 

 

上述流程中,job.waitForCompletion(true);是将任务提交给hadoop的jobtacker,然后剩下工作交给hadoop执行,那么任务是如何提交给jobtracker的呢:

 

1 如何连接到配置文件指定的jobtracker机器连接
找到waitForCompletion()--->submit();--->connect();--->JobClient---> init(conf);--->  createRPCProxy---> 
(JobSubmissionProtocol)RPC.getProxy--->JobSubmissionProtocol:Protocol that a JobClient and the central JobTracker use to communicate.
这样就和 执行任务的JobTracker连接上,如上流程是获取真实配置的jobtracker连接
2 如何将任务提交到指定的Jobtracker机器:
submit()--->jobClient.submitJobInternal(conf); ---> jobSubmitClient.submitJob(...)---> 客户端通过代理调用的是JobTracker服务端的submitJob方法--->然后
可以去JobTracker.submitJob(...)看任务执行流程



上述流程部分代码贴出如下:


private void connect() throws IOException, InterruptedException {
    ugi.doAs(new PrivilegedExceptionAction<Object>() {
      public Object run() throws IOException {
        jobClient = new JobClient((JobConf) getConfiguration());    
        return null;
      }
    });
  }
  
 看init():
   public void init(JobConf conf) throws IOException {
    String tracker = conf.get("mapred.job.tracker", "local"); // 从配置文件中获取Key为mapred.job.tracker对应的数据,若没有则用local替代
    tasklogtimeout = conf.getInt( //mapred.job.tracker属性在配置Hadoop  mapred-site.xml时必须填写,因此下面的local分支不会执行
      TASKLOG_PULL_TIMEOUT_KEY, DEFAULT_TASKLOG_TIMEOUT);
    this.ugi = UserGroupInformation.getCurrentUser();
    if ("local".equals(tracker)) {
      conf.setNumMapTasks(1);
      this.jobSubmitClient = new LocalJobRunner(conf);
    } else {
      this.rpcJobSubmitClient = 
          createRPCProxy(JobTracker.getAddress(conf), conf); // 通过rpc获取 jobtrack连接
      this.jobSubmitClient = createProxy(this.rpcJobSubmitClient, conf);
    }        
  }
  
  看submit():
    public void submit() throws IOException, InterruptedException, 
                              ClassNotFoundException {
    ensureState(JobState.DEFINE);
    setUseNewAPI();
    
    // Connect to the JobTracker and submit the job
    connect(); // 获取配置文件中配置好的jobtracker机器地址 eg: >master:9001
    info = jobClient.submitJobInternal(conf); // 提交到jobtracker机器
    super.setJobID(info.getID());
    state = JobState.RUNNING;
   }

 

 总结下如下:

(1)在eclipse中调用的job.waitForCompletion(true)实际上执行如下方法
	connect();
        info = jobClient.submitJobInternal(conf);
(2)在connect()方法中,实际上创建了一个JobClient对象。
	在调用该对象的构造方法时,获得了JobTracker的客户端代理对象JobSubmissionProtocol。
	JobSubmissionProtocol的实现类是JobTracker。
(3)在jobClient.submitJobInternal(conf)方法中,调用了
	JobSubmissionProtocol.submitJob(...),
	即执行的是JobTracker.submitJob(...)。

 

 

 

 

 

你可能感兴趣的:(map-reduce)