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. 基础才是重中之重~BouncyCastle实现的DES3加密~java通用

    对于BouncyCastle类库(包)来说,他提供了很多加密算法,在与.net和java进行相互加解密过程中,得到了不错的应用,本文以DES3为例,来说一下DES3加解密的过程. 加密过程 明文字符转 ...

  2. 拥抱 C/C++ : Android JNI 的使用

    编译工具 CMake 以及 Android 上 JNI 的使用介绍. 编译工具 CMake 在Android Studio 2.2 之后,工具中增加了 CMake 的支持,于是我们有两种选择来编译 c ...

  3. Flowable 简介

    一.Flowable 入门介绍 官网地址:https://www.flowable.org/ Flowable6.3中文教程:https://tkjohn.github.io/flowable-use ...

  4. JavaDailyReports10_18

    学习内容:HTML基本知识 1.通常标记具有默认属性,当一个标记中只有标记名时,使用默认属性. 2.HTML标记有两种:单标记和双标记,单标记的语法格式:  <标记名称/> 3.不同的属性 ...

  5. swap是干嘛的?

    本文截取自:http://hbasefly.com/2017/05/24/hbase-linux/ swap是干嘛的? 在Linux下,SWAP的作用类似Windows系统下的"虚拟内存&q ...

  6. psql: could not connect to server: No such file or directory

    postgresql报错: psql: could not connect to server: No such file or directory         Is the server run ...

  7. Vs2017编译器提示:不能将“const char *”类型的值分配到“char *”类型的实体

    在项目属性中将语言符合模式改成否即可

  8. Redis--部署操作

    1.Redis 1.1 安装 当前ubuntu虚拟机中已经安装好了redis,以下步骤可以跳过 以后自己安装过程如下:redis下载链接:x 指的是版本号 http://download.redis. ...

  9. [每日一题]面试官问:Async/Await 如何通过同步的方式实现异步?

    关注「松宝写代码」,精选好文,每日一题 ​时间永远是自己的 每分每秒也都是为自己的将来铺垫和增值 作者:saucxs | songEagle 一.前言 2020.12.23 日刚立的 flag,每日一 ...

  10. 实战Git命令(界面操作+命令行)

    先说明下公司的发版步骤,当需要开发一个新的功能,先从master分支中拉出一个自己的分支a(假设分支为a),在a分支开发功能完后,需要切换到dev分支,然后把自己的分支a合到dev分支,部署测试环境让 ...