小白也能学会的MapReduce编程

小白也能学会的MapReduce编程

再议MapReduce

我们知道hadoop的核心有四大组件:

  • HDFS
  • MapReduce
  • YARN
  • Common

HDFS:分布式存储系统
MapReduce:分布式计算系统
YARN: hadoop 的资源调度系统
Common: 以上三大组件的底层支撑组件,主要提供基础工具包和 RPC (远程过程调用,调用服务器的服务) 框架等

而MapReduce作为其中的计算系统在大规模数据处理时,有三个层面上的基本构思:

如何对付大数据处理:分而治之

对相互间不具有计算依赖关系的大数据,实现并行最自然的办法就是采取分而治之的策略

上升到抽象模型:Mapper与Reducer

MPI等并行计算方法缺少高层并行编程模型,为了克服这一缺陷,MapReduce借鉴了Lisp函数式语言中的思想,用Map和Reduce两个函数提供了高层的并行编程抽象模型

上升到构架:统一构架,为程序员隐藏系统层细节

MPI等并行计算方法缺少统一的计算框架支持,程序员需要考虑数据存储、划分、分发、结果收集、错误恢复等诸多细节;为此,MapReduce设计并提供了统一的计算框架,为程序员隐藏了绝大多数系统层面的处理细节

抽象描述Map与Reduce

MapReduce借鉴了函数式程序设计语言Lisp中的思想,定义了如下的Map和Reduce两个抽象的编程接口,由用户去编程实现:

map: (k1; v1) -> [(k2; v2)]

输入:键值对(k1; v1)表示的数据
处理:文档数据记录(如文本文件中的行,或数据表格中的行)将以“键值对”形式传入map函数;map函数将处理这些键值对,并以另一种键值对形式输出处理的一组键值对中间结果[(k2; v2)]
输出:键值对[(k2; v2)]表示的一组中间数据

reduce: (k2; [v2]) -> [(k3; v3)]

输入: 由map输出的一组键值对[(k2; v2)] 将被进行合并处理将同样主键下的不同数值合并到一个列表[v2]中,故reduce的输入为(k2; [v2])
处理:对传入的中间结果列表数据进行某种整理或进一步的处理,并产生最终的某种形式的结果输出[(k3; v3)] 。
输出:最终输出结果[(k3; v3)]

Map和Reduce为程序员提供了一个清晰的操作接口抽象描述

小结

  • 各个map函数对所划分的数据并行处理,从不同的输入数据产生不同的中间结果输出
  • 各个reduce也各自并行计算,各自负责处理不同的中间结果数据集合
  • 进行reduce处理之前,必须等到所有的map函数做完,因此,在进入reduce前需要有一个同步障(barrier);这个阶段也负责对map的中间结果数据进行收集整理(aggregation & shuffle)处理,以便reduce更有效地计算最终结果
  • 最终汇总所有reduce的输出结果即可获得最终结果

MapReduce编程详解

一个简单的WordCount程序

Mapper

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

/**
 * LongWritable 偏移量 long,表示该行在文件中的位置,而不是行号
 * Text map阶段的输入数据 一行文本信息 字符串类型 String
 * Text map阶段的数据字符串类型 String
 * IntWritable map阶段输出的value类型,对应java中的int型,表示行号
 */
public class WorkCountMap 
    extends Mapper<LongWritable, Text, Text, IntWritable>{
	/**
	 * key 输入的 键
	 * value 输入的 值
	 * context 上下文对象
	 */
	@Override
	protected void map(LongWritable key, Text value, Context context)
			throws IOException, InterruptedException {
		
		String line = value.toString();
		String[] words = line.split("/t");//分词
		for(String word : words) {
			Text wordText = new Text(word);
			IntWritable outValue = new IntWritable();
			//写出
			context.write(wordText, outValue);
		}
	}
}

  • 尖括号是JAVA的泛型,在这里约束了函数的输入数据类型
  • 上面代码中,注意Mapper类的泛型不是java的基本类型,而是Hadoop的数据类型Text、IntWritable。我们可以简单的等价为java的类String、int。
    代码中Mapper类的泛型依次是<k1,v1,k2,v2>。map方法的第二个形参是行文本内容,是我们关心的。核心代码是把行文本内容按照空格拆分,把每行数据提取出来,单词作为新的键,数量作为新的值,写入到上下文context中。在这里,因为有多组数据,因此每一组都会输出一个<wordText, outValue>键值对。

Reducer

reduce阶段的输入 是 mapper阶段的输出

import java.io.IOException;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
/**
 * Text  数据类型:字符串类型 String
 * IntWritable reduce阶段的输入类型 int 
 * Text reduce阶段的输出数据类型 String类型
 * IntWritable 输出词频个数 Int型
 */
public class WorkCountReduce extends Reducer<Text, IntWritable, Text, IntWritable>{
	/**
	 * key 输入的 键
	 * value 输入的 值
	 * context 上下文对象,用于输出键值对
	 */
	@Override
	protected void reduce(Text key, Iterable<IntWritable> value,
			Context context) throws IOException, InterruptedException {

		int sum=0;
		for (IntWritable number : value) {
			sum += number.get();
		}
		//单词  个数  hadoop,10
		context.write(key, new IntWritable(sum));
	}	
}

主函数

在 Hadoop 中一次计算任务称之为一个 job, main函数主要负责新建一个Job对象并为之设定相应的Mapper和Reducer类,以及输入、输出路径等

public static void main(String[] args) throws Exception 
{    //为任务设定配置文件 
    Configuration conf = new Configuration();	 
    //命令行参数 
   String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();	 
    if (otherArgs.length != 2) 
    {    System.err.println("Usage: wordcount <in> <out>");
         System.exit(2);
    } 	
    Job job = new Job(conf, “word count”);	//新建一个用户定义的Job
    job.setJarByClass(WordCount.class);	//设置执行任务的jar
    job.setMapperClass(WorkCountMap.class);	//设置Mapper类 
    job.setCombinerClass(WorkCountReduce.class);	//设置Combine类 
    job.setReducerClass(WorkCountReduce.class);	//设置Reducer类 
    job.setOutputKeyClass(Text.class);	//设置job输出的key
    //设置job输出的value 
    job.setOutputValueClass(IntWritable.class);	
    //设置输入文件的路径 
    FileInputFormat.addInputPath(job, new Path(otherArgs[0]));	
    //设置输出文件的路径 
    FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));	 
    //提交任务并等待任务完成  
    System.exit(job.waitForCompletion(true) ? 0 : 1);		 
}

收尾MapReduce核心运行机制

总说

一个完整的 mapreduce 程序在分布式运行时有两类实例进程:
(1) MRAppMaster:负责整个程序的过程调度及状态协调 (该进程在yarn节点上)
(2) Yarnchild:负责 map 阶段的整个数据处理流程
(3) Yarnchild:负责 reduce 阶段的整个数据处理流程

以上两个阶段 maptask 和 reducetask 的进程都是 yarnchild,并不是说这 maptask 和 reducetask 就跑在同一个 yarnchild 进行里(Yarnchild进程在运行该命令的节点上)

MapReduce程序的运行流程

(1) 一个 MapReduce 程序启动的时候,最先启动的是 MRAppMaster, MRAppMaster 启动后根据本次 job 的描述信息,计算出需要的 maptask 实例数量,然后向集群申请机器启动相应数量的 maptask 进程
(2) maptask 进程启动之后,根据给定的数据切片(哪个文件的哪个偏移量范围)范围进行数 据处理,主体流程为:
A、 利用客户指定的 inputformat 来获取 RecordReader 读取数据,形成输入 KV 对
B、 将输入 KV 对传递给客户定义的 map()方法,做逻辑运算,并将 map()方法输出的 KV 对收 集到缓存
C、 将缓存中的 KV 对按照 K 分区排序后不断溢写到磁盘文件 (超过缓存内存写到磁盘临时文件,最后都写到该文件,ruduce 获取该文件后,删除 )
(3) MRAppMaster 监控到所有 maptask 进程任务完成之后(真实情况是,某些 maptask 进 程处理完成后,就会开始启动 reducetask 去已完成的 maptask 处 fetch 数据),会根据客户指 定的参数启动相应数量的 reducetask 进程,并告知 reducetask 进程要处理的数据范围(数据
分区)
(4) Reducetask 进程启动之后,根据 MRAppMaster 告知的待处理数据所在位置,从若干台 maptask 运行所在机器上获取到若干个 maptask 输出结果文件,并在本地进行重新归并排序, 然后按照相同 key 的 KV 为一个组,调用客户定义的 reduce()方法进行逻辑运算,并收集运算输出的结果 KV,然后调用客户指定的 outputformat 将结果数据输出到外部存储

maptask并行度决定机制

maptask 的并行度决定 map 阶段的任务处理并发度,进而影响到整个 job 的处理速度。
一个 job 的 map 阶段并行度由客户端在提交 job 时决定, 客户端对 map 阶段并行度的规划
的基本逻辑为:
将待处理数据执行逻辑切片(即按照一个特定切片大小,将待处理数据划分成逻辑上的多 个 split),然后每一个 split 分配一个 mapTask 并行实例处理
这段逻辑及形成的切片规划描述文件,是由 FileInputFormat实现类的 getSplits()方法完成的。
该方法返回的是 List<InputSplit>, InputSplit 封装了每一个逻辑切片的信息,包括长度和位置 信息,而 getSplits()方法返回一组 InputSplit

猜你喜欢

转载自blog.csdn.net/TalesOV/article/details/105449785