MapReduce过程源码分析

Mapper

  首先mapper完成映射,将word映射成(word,1)的形式。

  MapReduce进程,Map阶段也叫MapTask,在MapTask中会通过run()方法来调用我们用户重写的mapper() 方法,

  分布式的运算程序往往需要分成至少两个阶段:Map阶段和Reduce阶段。

  第一个阶段,即Map阶段的maptask并发实例,完全并行独立运行,互不相干,如Map将要处理的多个文件的每个文件分成3份,分别放在集群中的各个数据节点,Map阶段中由maptask进程来处理已经存进来的文件,一行一行地去读数据,按空格切分行内单词,切分完毕之后,将单词统计出来以hashmap存储,其中以单词为key,以1作为单词的value。等到分配给自己的数据片全部读完之后,将这个hashmap按照首个字母的范围分成2个hashmap(分区排序),两个hashmap分别为:HashMap(a-p)和HashMap(q-z)。

  第二个阶段的reduce task并发实例互不相干,但是他们的数据依赖于上一个阶段的所有maptask的并发实例的输出。

reduce task 分别统计a-p开头的单词和q-z开头的单词,然后输出结果到文件。

  注意:MapReduce编程模型只能包含一个map阶段和一个reduce阶段,如果用户的业务逻辑非常复杂,那就只能多个MapReduce程序,串行执行。

  那么maptask如何进行任务分配?

  reducetask如何进行任务分配?

  maptask和reducetask之间如何衔接?

  如果maptask运行失败,如何处理?

  maptask如果都要自己负责输出数据的分区,很麻烦

  MrAPPMaster负责整个程序的过程调度及状态的协调。

  三个进程分别对应三个类:

  三个进程:

    1)MrAppMaster:负责整个程序的过程调度及状态协调

    2)MapTask:负责map阶段的整个数据处理流程

    3)ReduceTask:负责reduce阶段的整个数据处理流程

  分别对应的类:

    1)Driver阶段

    整个程序需要一个Drvier来进行提交,提交的是一个描述了各种必要信息的job对象

    2)Mapper阶段

    (1)用户自定义的Mapper要继承自己的父类

    (2)Mapper的输入数据是KV对的形式(KV的类型可自定义)

    (3)Mapper中的业务逻辑写在map()方法中

    (4)Mapper的输出数据是KV对的形式(KV的类型可自定义)

    (5)map()方法(maptask进程)对每一个<K,V>调用一次

    3)Reducer阶段

    (1)用户自定义的Reducer要继承自己的父类

    (2)Reducer的输入数据类型对应Mapper的输出数据类型,也是KV

    (3)Reducer的业务逻辑写在reduce()方法中

    (4)Reducetask进程对每一组相同k的<k,v>组调用一次reduce()方法

数据切片与MapTask并行度的决定机制

  1.一个Job的Map阶段并行度由客户端在提交Job时的切片数决定

  2.每一个Split切片分配一个MapTask并行实例处理

  3.默认情况下,切片大小=BlockSize

  4.切片时不靠路数据集整体,而是诸葛针对每一个文件单独切片。

job的提交过程分析

1 提交任务---->2 检查状态(if (state == JobState.DEFINE) {submit();})---->3.0 submit():3.1 确保job状态(ensureState(JobState.DEFINE))---->3.2 使用新的API(setUseNewAPI())---->3.3 连接集群(connect())---->3.4 根据get到的集群获取任务提交器(final JobSubmitter submitter = getJobSubmitter(cluster.getFileSystem(), cluster.getClient()))---->3.5 submitter提交任务(return submitter.submitJobInternal(Job.this, cluster))----->3.5.1检查输出路径是否设置以及输出路径是否存在(checkSpecs(jobs))---->3.5.2注册JobId(JobID jobId = submitClient.getNewJobID();)---->向目录拷贝一个文件,该文件就是我们之前(setJarByClass(xxx.class))设置好的jar包---->客户端就是通过该方法调用InputFormat来给我们的输入文件进行切片---->切片之后,执行conf.setInt(MRJobConfig.NUM_MAPS, maps);将切片信息写入到目录(submitJobDir)中---->把job的配置信息conf也提交到submitDir---->任务正式提交。

首先我们调用:

boolean b = job.waitForCompletion(true);

该方法的主体:

public boolean waitForCompletion(boolean verbose
) throws IOException, InterruptedException,
ClassNotFoundException {
if (state == JobState.DEFINE) {
submit();
}
if (verbose) {
monitorAndPrintJob();
} else {
// get the completion poll interval from the client.
int completionPollIntervalMillis =
Job.getCompletionPollInterval(cluster.getConf());
while (!isComplete()) {
try {
Thread.sleep(completionPollIntervalMillis);
} catch (InterruptedException ie) {
}
}
}
return isSuccessful();
}

然后调用submit()方法:

/**
* Submit the job to the cluster and return immediately.
* @throws IOException
*/
public void submit()
throws IOException, InterruptedException, ClassNotFoundException {
ensureState(JobState.DEFINE);
setUseNewAPI();
connect();
final JobSubmitter submitter =
getJobSubmitter(cluster.getFileSystem(), cluster.getClient());
status = ugi.doAs(new PrivilegedExceptionAction<JobStatus>() {
public JobStatus run() throws IOException, InterruptedException,
ClassNotFoundException {
return submitter.submitJobInternal(Job.this, cluster);
}
});
state = JobState.RUNNING;
LOG.info("The url to track the job: " + getTrackingURL());
}

在submit()方法中,通过ensureState(JobState.DEFINE)方法再次确认job的状态是否为DEFINE,如果是,就设置使用新的API(hadoop2.x版本升级了很多新的API,而老的版本调用MapReduce程序的时候,在这里自动转成新的API),然后调用connect()方法建立和yarn集群的连接。

connect()方法的内容如下:

 private synchronized void connect()
throws IOException, InterruptedException, ClassNotFoundException {
if (cluster == null) {
cluster =
ugi.doAs(new PrivilegedExceptionAction<Cluster>() {
public Cluster run()
throws IOException, InterruptedException,
ClassNotFoundException {
return new Cluster(getConfiguration());
}
});
}
}

connect()方法中,首先判断cluster,如果集群为null,那么就返回一个新的集群,return new Cluster(getConfiguration());,如果任务的配置是本地模式就是一个LocalMaster,如果是yarn集群就是YarnMaster。

连接到cluster之后,然后就可以提交我们要执行的任务了,这时执行

final JobSubmitter submitter = getJobSubmitter(cluster.getFileSystem(), cluster.getClient());

通过返回的集群的客户端和协议来获得一个submitter,然后执行语句

return submitter.submitJobInternal(Job.this, cluster);

利用submitter来真正的提交我们的job任务,submitJobInternal(Job.this, cluster)这个方法是真正提交job任务的方法,该方法的内容见文章最后。

然后通过checkSpecs(jobs)方法检查输出路径是否设置以及输出路径是否存在,然后执行语句:

Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);根据我们设置的cluster获取我们的stagingDir,即存放MapReduce过程中产生数据的临时文件夹。

然后执行JobID jobId = submitClient.getNewJobID();对我们的job进行注册,进而得到JobId,通过job.setJobID(jobId);设置我们注册好的jobId,然后执行copyAndConfigureFiles(job, submitJobDir);向目录拷贝一个文件,该文件就是我们之前(setJarByClass(xxx.class))设置好的jar包,然后执行int maps = writeSplits(job, submitJobDir);客户端就是通过该方法调用InputFormat来给我们的输入文件进行切片,切片之后,执行conf.setInt(MRJobConfig.NUM_MAPS, maps);将切片信息写入到目录(submitJobDir)中。所谓切片信息就是标注了哪台机器处理哪个切片数据,相当于一个索引信息,有了这个索引信息,MapReduce的APPMaster在执行任务的时候就可以知道启动几个maptask并且知道每个机器处理哪一个部分的数据,所以这个信息也是要提交到HDFS的临时目录里面。

然后执行writeConf(conf, submitJobFile);把我们的job的配置信息conf也提交到submitDir,执行完之后,临时目录中生成job.xml(存放job的配置信息,包括集群的各种手动配置及默认配置信息)。

其实到此为止,这一系列的工作都是在为集群的工作做准备,集群中创建一个临时目录,它是可以供集群中所有的数据节点进行访问的,首先在集群的临时目录中存放了jar包,然后放置了切片信息,最后又放置了配置文件,这样maptask可以到临时文件夹中读取存放的这三个信息,进而执行他们各自的任务。

然后程序第240行真正进行job的提交,然后任务开始运行。

至此程序执行完毕。

总结

任务的提交过程:首先是检查任务的状态,检查输出目录,都没问题之后,然后开始连接集群,因为任务都是交由集群中的其他人来执行,所以其他人需要得知这个任务的必要信息,因此job提交的时候有必要将这些任务的必要信息提交到大家都可以访问的临时目录(在HDFS上),这些必要的信息包括:jar包、切片信息以及配置信息(job.xml)。

附:submitJobInternal(Job.this, cluster)方法原代码:

/**
* Internal method for submitting jobs to the system.
*
* <p>The job submission process involves:
* <ol>
* <li>
* Checking the input and output specifications of the job.
* </li>
* <li>
* Computing the {@link InputSplit}s for the job.
* </li>
* <li>
* Setup the requisite accounting information for the
* {@link DistributedCache} of the job, if necessary.
* </li>
* <li>
* Copying the job's jar and configuration to the map-reduce system
* directory on the distributed file-system.
* </li>
* <li>
* Submitting the job to the <code>JobTracker</code> and optionally
* monitoring it's status.
* </li>
* </ol></p>
* @param job the configuration to submit
* @param cluster the handle to the Cluster
* @throws ClassNotFoundException
* @throws InterruptedException
* @throws IOException
*/
JobStatus submitJobInternal(Job job, Cluster cluster)
throws ClassNotFoundException, InterruptedException, IOException { //validate the jobs output specs
checkSpecs(job); Configuration conf = job.getConfiguration();
addMRFrameworkToDistributedCache(conf); Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
//configure the command line options correctly on the submitting dfs
InetAddress ip = InetAddress.getLocalHost();
if (ip != null) {
submitHostAddress = ip.getHostAddress();
submitHostName = ip.getHostName();
conf.set(MRJobConfig.JOB_SUBMITHOST,submitHostName);
conf.set(MRJobConfig.JOB_SUBMITHOSTADDR,submitHostAddress);
}
JobID jobId = submitClient.getNewJobID();
job.setJobID(jobId);
Path submitJobDir = new Path(jobStagingArea, jobId.toString());
JobStatus status = null;
try {
conf.set(MRJobConfig.USER_NAME,
UserGroupInformation.getCurrentUser().getShortUserName());
conf.set("hadoop.http.filter.initializers",
"org.apache.hadoop.yarn.server.webproxy.amfilter.AmFilterInitializer");
conf.set(MRJobConfig.MAPREDUCE_JOB_DIR, submitJobDir.toString());
LOG.debug("Configuring job " + jobId + " with " + submitJobDir
+ " as the submit dir");
// get delegation token for the dir
TokenCache.obtainTokensForNamenodes(job.getCredentials(),
new Path[] { submitJobDir }, conf); populateTokenCache(conf, job.getCredentials()); // generate a secret to authenticate shuffle transfers
if (TokenCache.getShuffleSecretKey(job.getCredentials()) == null) {
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance(SHUFFLE_KEYGEN_ALGORITHM);
keyGen.init(SHUFFLE_KEY_LENGTH);
} catch (NoSuchAlgorithmException e) {
throw new IOException("Error generating shuffle secret key", e);
}
SecretKey shuffleKey = keyGen.generateKey();
TokenCache.setShuffleSecretKey(shuffleKey.getEncoded(),
job.getCredentials());
}
if (CryptoUtils.isEncryptedSpillEnabled(conf)) {
conf.setInt(MRJobConfig.MR_AM_MAX_ATTEMPTS, 1);
LOG.warn("Max job attempts set to 1 since encrypted intermediate" +
"data spill is enabled");
} copyAndConfigureFiles(job, submitJobDir); Path submitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir); // Create the splits for the job
LOG.debug("Creating splits at " + jtFs.makeQualified(submitJobDir));
int maps = writeSplits(job, submitJobDir);
conf.setInt(MRJobConfig.NUM_MAPS, maps);
LOG.info("number of splits:" + maps); // write "queue admins of the queue to which job is being submitted"
// to job file.
String queue = conf.get(MRJobConfig.QUEUE_NAME,
JobConf.DEFAULT_QUEUE_NAME);
AccessControlList acl = submitClient.getQueueAdmins(queue);
conf.set(toFullPropertyName(queue,
QueueACL.ADMINISTER_JOBS.getAclName()), acl.getAclString()); // removing jobtoken referrals before copying the jobconf to HDFS
// as the tasks don't need this setting, actually they may break
// because of it if present as the referral will point to a
// different job.
TokenCache.cleanUpTokenReferral(conf); if (conf.getBoolean(
MRJobConfig.JOB_TOKEN_TRACKING_IDS_ENABLED,
MRJobConfig.DEFAULT_JOB_TOKEN_TRACKING_IDS_ENABLED)) {
// Add HDFS tracking ids
ArrayList<String> trackingIds = new ArrayList<String>();
for (Token<? extends TokenIdentifier> t :
job.getCredentials().getAllTokens()) {
trackingIds.add(t.decodeIdentifier().getTrackingId());
}
conf.setStrings(MRJobConfig.JOB_TOKEN_TRACKING_IDS,
trackingIds.toArray(new String[trackingIds.size()]));
} // Set reservation info if it exists
ReservationId reservationId = job.getReservationId();
if (reservationId != null) {
conf.set(MRJobConfig.RESERVATION_ID, reservationId.toString());
} // Write job file to submit dir
writeConf(conf, submitJobFile); //
// Now, actually submit the job (using the submit name)
//
printTokens(jobId, job.getCredentials());
status = submitClient.submitJob(
jobId, submitJobDir.toString(), job.getCredentials());
if (status != null) {
return status;
} else {
throw new IOException("Could not launch job");
}
} finally {
if (status == null) {
LOG.info("Cleaning up the staging area " + submitJobDir);
if (jtFs != null && submitJobDir != null)
jtFs.delete(submitJobDir, true); }
}
}

MapReduce过程源码分析的更多相关文章

  1. [Android]从Launcher开始启动App流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...

  2. [Android]Android系统启动流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...

  3. Android系统默认Home应用程序(Launcher)的启动过程源码分析

    在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...

  4. Android Content Provider的启动过程源码分析

    本文參考Android应用程序组件Content Provider的启动过程源码分析http://blog.csdn.net/luoshengyang/article/details/6963418和 ...

  5. Android应用程序绑定服务(bindService)的过程源码分析

    Android应用程序组件Service与Activity一样,既能够在新的进程中启动,也能够在应用程序进程内部启动:前面我们已经分析了在新的进程中启动Service的过程,本文将要介绍在应用程序内部 ...

  6. Spring加载流程源码分析03【refresh】

      前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...

  7. 【高速接口-RapidIO】5、Xilinx RapidIO核例子工程源码分析

    提示:本文的所有图片如果不清晰,请在浏览器的新建标签中打开或保存到本地打开 一.软件平台与硬件平台 软件平台: 操作系统:Windows 8.1 64-bit 开发套件:Vivado2015.4.2 ...

  8. 转:Spring与Mybatis整合的MapperScannerConfigurer处理过程源码分析

    原文地址:Spring与Mybatis整合的MapperScannerConfigurer处理过程源码分析 前言 本文将分析mybatis与spring整合的MapperScannerConfigur ...

  9. 5.Xilinx RapidIO核例子工程源码分析

    https://www.cnblogs.com/liujinggang/p/10091216.html 一.软件平台与硬件平台 软件平台: 操作系统:Windows 8.1 64-bit 开发套件:V ...

随机推荐

  1. 一个简单的java项目使用hibernate连接mysql数据库

    实体类与表对应文件Customer.hbm.xml <?xml version="1.0" encoding="UTF-8"?><!DOCTY ...

  2. eclips快捷键

    所谓"工欲善其事必先利其器",程序写多了,对于快捷键总有些特别的偏爱.在众多编辑器中,Eclipse算是用的比较多,也是最熟的. 最常用(也是最爱的:)) Ctrl+' :  自动 ...

  3. Mac电脑jsp连接mysql

    四个步骤教你如何用jsp代码连接mysql 第一步:下载jdbc驱动 进入mysql官网:https://dev.mysql.com/downloads/connector/ 找到Connect/J ...

  4. ThreadLocal源码深度剖析

    ThreadLocal源码深度剖析 ThreadLocal的作用 ThreadLocal的作用是提供线程内的局部变量,说白了,就是在各线程内部创建一个变量的副本,相比于使用各种锁机制访问变量,Thre ...

  5. elasticsearch迁移工具--elasticdump的使用

    这篇文章主要讨论使用Elasticdump工具做数据的备份和type删除. Elasticsearch的备份,不像MYSQL的myslqdump那么方便,它需要一个插件进行数据的导出和导入进行备份和恢 ...

  6. Spark sql 简单使用

    一.认识Spark sql 1.什么是Sparksql? spark sql是spark的一个模块,主要用于进行结构化数据的处理,它提供的最核心抽象就是DataFrame. 2.SparkSQL的作用 ...

  7. Hbase-cdh5.14.2与kylin集成异常

    1.原先使用版本:apache-kylin-2.5.1-bin-hbase1x 原生版本 启动报错出现异常: Failed to find metadata store by url: kylin_m ...

  8. 对于k8s微服务的性能测试监控平台搭建

    之前有写过对于传统项目的性能测试监控,但是对于目前市场占比已经很低,大部分项目使用k8s,今天讲一下对于k8s如何去监控. 对于k8s的监控我们所有的操作都要在master下进行. 一.部署grafa ...

  9. 【对线面试官】Java 反射&&动态代理

    // 抽象类,定义泛型<T> public abstract class BaseDao<T> { public BaseDao(){ Class clazz = this.g ...

  10. Laya 踩坑日记-BitmapFont 不显示空格

    项目中有用到艺术字,美术通过 bmfont64 将字体导给我了,结果发现在应用上 空格不显示 如图: 今天去深究了一下这个问题,发现是底层没封装好,然后自己改了一下下面是改过的 BitmapFont ...