5 MapReduce
5.1 设计理念
计算向数据靠拢,减小节点间的数据移动开销
- 前提条件
● 待处理的数据集可以分解为许多小数据集
● 每个小数据集都可以完全地并行处理
5.2 基础架构
MapReduce框架包含一个Master和若干个Slave。Master上运行JobTracker,负责作业和任务的调度,并监控它们的执行;Slave上运行TaskTracker,负责执行由JobTracker指派的任务。
通常,TaskTracker与DataNode运行于同一节点,JobTracker与NameNode可以运行于同一节点,也可以不运行于同一节点。
5.3 工作流程概述
(1)首先使用InputFormat模块做Map前预处理,将输入文件进行逻辑切分,得到基本输入单位InputSplit(InputSplit中记录了要处理数据的位置和长度);
(2)RecorderReader根据InputSplit中的信息,加载数据,并转换为适合Map输入的键值对;
(3)Map任务对数据进行处理,输出一系列<key, value>
作为中间结果;
(4)Shuffle过程:对中间结果进行分区、排序、合并、归并等操作,得到<key, value-list>
;此过程在Map端和Reduce端都有执行;
(5)Reduce过程将Shutffle的结果作为输入,执行用户定义的逻辑,输出结果给OutputFormat模块;
(6)OutputFormat模块对输出目录以及输出结果类型进行验证,验证通过后将结果输出到DSF中。
6 shuffle过程
6.1 过程简介
6.2 Map端shuffle过程
(1)Map处理:Map任务接收<key, value>
作为输入,按一定规则转换成<key, value>
进行输出。
(2)写入缓存:Map任务的输出结果首先被写入缓存,缓存默认大小是100MB,溢写比例是0.8,即当缓存使用80MB时就开始溢写操作,保证了Map结果可以一直写入缓存而不受溢写过程的影响。
(3)溢写操作:溢写到磁盘之前,会依次进行分区(Partition)、排序(Sort)、合并(Combine)等操作,操作结束后会写入磁盘并清空缓存:
(1)MapReduce通过Partitioner接口对
<k,v>
进行分区,默认分区规则是hash(key) mod R,把结果分给R个Reduce任务;
(2)对每个分区内的所有<k, v>
,后台进程根据key进行排序;
(3)排序过后是一个可选的合并操作,需要用户事先定义Combiner函数。将具有相同键的<k1, v1>,<k1, v2>
的value相加,得到<k, v1+v2>
,减小溢写数据量。并非所有场合都能使用Combiner操作;
(4)归并(Merge):对磁盘中的多个溢写文件进行归并操作,生成一个大的溢写文件。对具有相同key的<k1, v1>、<k1, v2>
归并成一个新的键值对<k1, <v1,v2>>
。
6.3 Reduce端shuffle过程
(1)领取数据:每个Reduce任务会不断地通过RPC向JobTracker询问Map任务是否已经完成,一旦收到JobTracker的通知,Reduce任务就会到该Map任务所在的机器上把属于自己处理的分区数据领取到Reduce任务的缓存中。
(2)归并数据:缓存中的数据来自于不同的Map任务,缓存占满时启动溢写操作。首先对<k, v>
进行归并;然后进行合并操作,减少写入磁盘数据量,最后将数据写入磁盘生成溢写文件。磁盘中的多个溢写文件会被再次归并成一个大文件,每轮归并操作可以归并的文件数量由参数io.sort.factor决定,默认是10。
(3)Reduce处理:归并后的大文件直接输入给Reduce任务,执行reduce函数。
6.4 sort过程
在MapReduce计算框架中主要用到了两种排序方法:快速排序和归并排序。在Map任务和Reduce任务过程中,一共发生了3次排序操作。
(1)在Map端溢写操作中,存在分区、排序、合并等操作,其中包含一次排序过程。是在缓冲区进行的内排序,属于快速排序;
(2)在Map任务完成之前,会对Map端磁盘中的多个溢写文件进行归并操作,由于单个溢写文件已经有序,所以本次进行的是归并排序;
(3)Reduce端从多个Map端领回溢写文件后,需要对这些溢写文件进行再次排序,属于归并排序。
6.5 MapReduce任务的分配
(1)任务槽
每个TaskTracker有固定数量的任务槽(slot)来处理Map和Reduce,任务槽的数量由计算机的内核数和内存大小决定的。调度器只有在TaskTracker的Map槽被map任务填满的情况下,才会分配reduce任务到Reduce槽。
(2)本地化
Map任务过程中涉及数据本地化和机架本地化:数据本地化是指任务在输入数据分片所在的计算机上运行,即“移动计算比移动数据划算”;机架本地化是指任务和输入数据分片不在同一个节点上,但在同一个机架上。JobTracker运行Map任务时,首先选择数据本地化,然后选择机架本地化。
Reduce任务不需要考虑本地化问题,因为可能有多个Map任务输出到一个Reduce任务来处理。
6.6 任务的分配
(1)推测执行
多任务在多节点执行时,很有可能因为某个任务执行效率非常缓慢,出现“拖后腿“的现象。推测执行是指当检测到有任务比预期慢时,会调度空闲的节点执行剩余任务的复制,优化执行过程。
当任务完成时,节点向JobTracker通告,如果其他的复制任务还在执行,则JobTracker通知TaskTracker结束这些任务并丢弃它们。
推测执行虽然可以减少作业的执行时间,但却降低了集群的执行效率,减少了吞吐量。推测执行默认是开启的,Job类的setSpeculativeExecution(boolean speculativeExecution)
方法可以设置是否开启推测执行。
(2)JVM重用
当每个任务的执行时间都很短时,可以重用JVM来优化性能。JVM重用可以使同一个job的一些静态数据得到共享,从而提升集群性能。但JVM重用也会带来JVM中碎片增加问题,这对JVM来说影响不是很大。
jobconf中的setNumTasksToExcutePerJvm()
方法也可以使用上次的值。
(3)跳过坏的记录
Hadoop检测出来的坏记录以序列文件的方式保存在_log/skip,在作业完成后可以查看这些记录。
6.7 MapReduce的Java API
(1)org.apache.hadoop.mapreduce.Job
public class Job extends org.apache.hadoop.mapreduce.task.JobContextImpl implements JobContext {
//获取实例
public static Job getInstance(Configuration conf) throws IOException {}//
public static Job getInstance(Configuration conf, String jobName) throws IOException{}
public static Job getInstance(JobStatus status, Configuration conf) throws IOException {}
//获取属性字段
public long getStartTime(){}
public long getFinishTime() throws IOException, InterruptedException{}
public JobPriority getPriority() throws IOException, InterruptedException
public String getJobName(){}
public String getJobFile(){}
//set方法
/********** 必要方法 **************/
public void setJarByClass(Class<?> cls){}
public void setMapperClass(Class<? extends Mapper> cls) throws IllegalStateException{}
public void setReducerClass(Class<? extends Reducer> cls) throws IllegalStateException{}
public void setOutputKeyClass(Class<?> theClass) throws IllegalStateException {}
public void setOutputValueClass(Class<?> theClass) throws IllegalStateException {}
/*********** 可选方法 *************/
public void setInputFormatClass(Class<? extends InputFormat> cls) throws IllegalStateException {}
public void setOutputFormatClass(Class<? extends OutputFormat> cls) throws IllegalStateException{}
public void setMapOutputKeyClass(Class<?> theClass) throws IllegalStateException{}
public void setMapOutputValueClass(Class<?> theClass) throws IllegalStateException{}
public void setPartitionerClass(Class<? extends Partitioner> cls) throws IllegalStateException
public void setCombinerClass(Class<? extends Reducer> cls) throws IllegalStateException
public void setCombinerKeyGroupingComparatorClass(Class<? extends RawComparator> cls) throws IllegalStateException {}
public void setSortComparatorClass(Class<? extends RawComparator> cls) throws IllegalStateException //定义Map结果中key的比较规则
public void setGroupingComparatorClass(Class<? extends RawComparator> cls) throws IllegalStateException
public void setPriority(JobPriority priority) throws IOException, InterruptedException{}
public void setNumReduceTasks(int tasks) throws IllegalStateException{}
public void setJar(String jar){} //Set the job jar
public void setJobName(String name) throws IllegalStateException{}
//提交任务
public void submit() throws IOException, InterruptedException, ClassNotFoundException{}
public boolean waitForCompletion(boolean verbose) throws IOException, InterruptedException, ClassNotFoundException {}
//条件判断
public boolean isComplete() throws IOException {}
public boolean isSuccessful() throws IOException {}
}
(2)org.apache.hadoop.mapreduce.lib.input.FileInputFormat
public abstract class FileInputFormat<K,V> extends InputFormat<K,V> {
public static void setInputPaths(Job job, String commaSeparatedPaths) throws IOException {}
public static void setInputPaths(Job job, Path... inputPaths) throws IOException {}
public static void addInputPaths(Job job, String commaSeparatedPaths) throws IOException {}
public static void addInputPath(Job job, Path path) throws IOException {}
public static Path[] getInputPaths(JobContext context) {}
}
(3)org.apache.hadoop.mapreduce.lib.output.FileOutputFormat
public abstract class FileOutputFormat<K,V> extends OutputFormat<K,V> {
public static void setOutputPath(Job job, Path outputDir) {}
public static Path getOutputPath(JobContext job) {}
}
6.8 WordCount示例
(1)Mapper的实现类
import java.io.IOException;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class MyMapper extends Mapper<Object, Text, Text, IntWritable> {
private final IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object ikey, Text ivalue, Context context) throws IOException, InterruptedException {
String[] words = ivalue.toString().split(" ");
for (String item : words) {
word.set(item);
context.write(word, one);
}
}
}
(2)Reducer的实现类
import java.io.IOException;
import org.apache.hadoop.io.*;
import org.apache.hadoop.mapreduce.Reducer;
public class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// process values
int count = 0;
for (IntWritable val : values) {
count += val.get();
}
context.write(key, new IntWritable(count));
}
}
(3)Driver的实现类
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
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;
public class MyDriver {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "JobName");
job.setJarByClass(MyDriver.class);
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
// TODO: specify output types
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// TODO: specify input and output DIRECTORIES (not files)
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
boolean res = job.waitForCompletion(true);
System.exit(res ? 0 : 1);
}
}