MapReduce过程源码分析
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过程源码分析的更多相关文章
- [Android]从Launcher开始启动App流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...
- [Android]Android系统启动流程源码分析
以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...
- Android系统默认Home应用程序(Launcher)的启动过程源码分析
在前面一篇文章中,我们分析了Android系统在启动时安装应用程序的过程,这些应用程序安装好之后,还须要有一个Home应用程序来负责把它们在桌面上展示出来,在Android系统中,这个默认的Home应 ...
- Android Content Provider的启动过程源码分析
本文參考Android应用程序组件Content Provider的启动过程源码分析http://blog.csdn.net/luoshengyang/article/details/6963418和 ...
- Android应用程序绑定服务(bindService)的过程源码分析
Android应用程序组件Service与Activity一样,既能够在新的进程中启动,也能够在应用程序进程内部启动:前面我们已经分析了在新的进程中启动Service的过程,本文将要介绍在应用程序内部 ...
- Spring加载流程源码分析03【refresh】
前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...
- 【高速接口-RapidIO】5、Xilinx RapidIO核例子工程源码分析
提示:本文的所有图片如果不清晰,请在浏览器的新建标签中打开或保存到本地打开 一.软件平台与硬件平台 软件平台: 操作系统:Windows 8.1 64-bit 开发套件:Vivado2015.4.2 ...
- 转:Spring与Mybatis整合的MapperScannerConfigurer处理过程源码分析
原文地址:Spring与Mybatis整合的MapperScannerConfigurer处理过程源码分析 前言 本文将分析mybatis与spring整合的MapperScannerConfigur ...
- 5.Xilinx RapidIO核例子工程源码分析
https://www.cnblogs.com/liujinggang/p/10091216.html 一.软件平台与硬件平台 软件平台: 操作系统:Windows 8.1 64-bit 开发套件:V ...
随机推荐
- HTTP Error 405.0 - Method Not Allowed 无法显示您正在查找的页面,因为使用了无效方法(HTTP 谓词)。
将submit改成button即可 因为触发了form表单
- bootstrap(一)栅格系统
中文网:http://www.bootcss.com/ 官网:http://v3.bootcss.com/ 需要准备:离线手册 和 软件包 项目中引用bootstrap.min.js压缩版和boo ...
- MySQL如何优雅的删除大表
前言 删除表,大家下意识想到的命令可能是直接使用DROP TABLE "表名",这是初生牛犊的做法,因为当要删除的表达空间到几十G,甚至是几百G的表时候.这样一条命令下去,MySQ ...
- python序列(三)列表元素访问与计数
1.使用下标直接访问列表元素,如果指定下标不存在,则抛出异常. >>> alist[3] 1 >>> alist[3]=5.5 >>> alist ...
- java.io.IOException: Target host must not be null, or set in parameters. scheme=null, host=null, path=/
使用的 xutils 出现标题中的错误 原因:没有添加 Cookie 1 params.addHeader("Cookie", CurrentUserSettings.getCoo ...
- 5.innodb B+tree索引
索引基础 索引是数据结构 1.图例 2.B+tree 特征 1.非叶子节点不保存数据,只用来索引,数据都保存在叶子节点 2.查询任何一条数据,查询的索引深度都是一样的 3. B+ 树中各个页之间是通过 ...
- Liunx运维(六)-文件备份与压缩命令
文档目录: 一.tar:打包备份 二.gzip:压缩或解压文件 三.zip:打包和压缩文件 四.unzip:解压zip文件 五.scp:远程文件复制 六.rsync:文件同步工具 ---------- ...
- DVWA Brute Force:暴力破解篇
DVWA Brute Force:暴力破解篇 前言 暴力破解是破解用户名密码的常用手段,主要是利用信息搜集得到有用信息来构造有针对性的弱口令字典,对网站进行爆破,以获取到用户的账号信息,有可能利用其权 ...
- 数据库零基础之---了解数据库的事务[ACID]
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功. 我们先举一个例子来描述一下事务: 假设要张三通过银行给李四进行转账1000元钱,张三原有余额10000元整,李四有人民币 ...
- Flink学习之路(一)Flink简介
一.什么是Flink? Apache Flink是一个面向分布式数据流处理和批量数据处理的开源计算平台,提供支持流处理和批处理两种类型应用的功能. 二.Flink特点 1.现有的开源计算方案,会把流处 ...