Hadoop随笔(一):工作流程的源码
一、几个可能会用到的属性值
1、mapred.map.tasks.speculative.execution和mapred.reduce.tasks.speculative.execution
这两个属性可以决定Map任务和Reduce任务是否开启推测式执行策略。推测式执行策略在Hadoop中用来应对执行缓慢的任务所造成的瓶颈,但是对代码缺陷所导致的任务执行过慢,推测执行是一种反向的作用,应当避免,而Hadoop默认是开启推测式执行的。
2、mapred.job.reuse.jvm.num.tasks
这个属性值默认为1,表示一个JVM上只运行一个任务。如果Task Tracker具有很多小任务,重复启动JVM是非常耗时的,可以考虑任务JVM重用。将属性值设置为大于1的值表示启用了JVM;设置为-1表示共享此JVM的任务数目不受限制。尽管启用了JVM重用,但是Task Tracker上的多个任务还是顺序执行的,只是不需要额外启动JVM而已。
3、Task Tracker的本地参数值
名称 | 类型 | 描述 |
mapred.job.id | String | job id |
mapred.jar | String | job目录下job.jar的位置 |
job.local.dir | String | job指定的共享存储空间 |
mapred.tip.id | String | task id |
mapred.task.id | String | task尝试id |
mapred.task.partition | int | task在job中的id |
map.input.file | String | map读取的文件名 |
map.input.start | long | map输入的数据块的起始位置的偏移 |
map.input.length | long | map输入的数据块的字节数 |
mapred.work.output.dir | String | task临时输出目录 |
二、Hadoop工作流程
以下Hadoop源码来源于hadoop-1.2.1。
1、作业的提交
hadoop项目的第一步自然是根据需求进行代码编写,而后是需要配置Map类、Reduce类、Input路径、Output路径以及其他的虚拟机配置等。但这些操作只是为hadoop形成了一个作业,还算不上Hadoop的工作流程,将项目提交执行后,hadoop进入完全自动的运行方式,工作流程才开始启动。
作业的提交代码实现于JobClient类(JobClient.java)的submitJobInternal方法,分为以下几个步骤:
a.获取Job的ID
JobID jobId = jobSubmitClient.getNewJobId();
b.分配job在HDFS中的资源空间,配置其路径
Path submitJobDir = new Path(jobStagingArea, jobId.toString());
jobCopy.set("mapreduce.job.dir", submitJobDir.toString()); //jobCopy是拷贝的一个job副本
JobStatus status = null;
c.监控地址令牌,直到可以对该空间进行操作。
TokenCache.obtainTokensForNamenodes(jobCopy.getCredentials(),new Path [] {submitJobDir},jobCopy);
Path submitJobFile = JobSubmissionFiles.getJobConfPath(submitJobDir);
int reduces = jobCopy.getNumReduceTasks();
InetAddress ip = InetAddress.getLocalHost();
if (ip != null) {
job.setJobSubmitHostAddress(ip.getHostAddress());
job.setJobSubmitHostName(ip.getHostName());
}
JobContext context = new JobContext(jobCopy, jobId);
d.检查output类型
if (reduces == 0 ? jobCopy.getUseNewMapper() :
jobCopy.getUseNewReducer()) {
org.apache.hadoop.mapreduce.OutputFormat<?,?> output =
ReflectionUtils.newInstance(context.getOutputFormatClass(),
jobCopy);
output.checkOutputSpecs(context);
} else {
jobCopy.getOutputFormat().checkOutputSpecs(fs, jobCopy);
}
e.为job划分splits
FileSystem fs = submitJobDir.getFileSystem(jobCopy);
LOG.debug("Creating splits at " + fs.makeQualified(submitJobDir));
int maps = writeSplits(context, submitJobDir);
jobCopy.setNumMapTasks(maps);
f.在job的配置文件中写入相关的排队、配置信息
String queue = jobCopy.getQueueName();
AccessControlList acl = jobSubmitClient.getQueueAdmins(queue);
jobCopy.set(QueueManager.toFullPropertyName(queue,QueueACL.ADMINISTER_JOBS.getAclName()), acl.getACLString());
g.将job的信息通知给JobTrack
FSDataOutputStream out = FileSystem.create(fs, submitJobFile,new FsPermission(JobSubmissionFiles.JOB_FILE_PERMISSION));
f.将job写入到HDFS中。需要注意的是,写入job之前要先将cache中的数据清除。这样做的原因:cache中的数据对当前job是没有用的了,如果在job写入之前清除是没有影响的;但是,如果在写入之后清除,cache中的很可能已经被更改为其他job的信息了,清除cache中的数据会破坏其他的job。
TokenCache.cleanUpTokenReferral(jobCopy);
try {
jobCopy.writeXml(out);
} finally {
out.close();
}
g.调用submitJob类来真正的提交job
printTokens(jobId, jobCopy.getCredentials());
status = jobSubmitClient.submitJob(jobId, submitJobDir.toString(), jobCopy.getCredentials());
JobProfile prof = jobSubmitClient.getJobProfile(jobId);
if (status != null && prof != null) {
return new NetworkedJob(status, prof, jobSubmitClient);
} else {
throw new IOException("Could not launch job");
}
至此,一个作业就被完整地提交给JobTrack和HDFS了。
2、作业的初始化
作业的初始化主要是考虑到map和reduce都需要进行初始化,因此需要注意其中的细节。
作业的初始化源码实现于JobInprogress类(JobInProgress.java)的initTask方法。作业初始化的步骤如下:
a.首先需要获取job的一些配置信息和运行信息
if (!jobtracker.getConf().getBoolean(JT_JOB_INIT_EXCEPTION_OVERRIDE, false) && getJobConf().getBoolean(JOB_INIT_EXCEPTION, false)) {
waitForInitWaitLockForTests();
}
if (tasksInited || isComplete()) {
return;
}
synchronized(jobInitKillStatus){ //互锁操作
if(jobInitKillStatus.killed || jobInitKillStatus.initStarted) {
return;
}
jobInitKillStatus.initStarted = true;
}
b.获取job的执行信息和优先级信息
c.根据input splits的数目决定map的数目,每一个splits都需要一个map
TaskSplitMetaInfo[] splits = createSplits(jobId);
if (numMapTasks != splits.length) {
throw new IOException("Number of maps in JobConf doesn't match number of " +
"recieved splits for job " + jobId + "! " +
"numMapTasks=" + numMapTasks + ", #splits=" + splits.length);
}
numMapTasks = splits.length;
d.检查splits的位置,保证创建的map/reduce或者初始化的map/reduce都是有意义的,同时还需要在监控进程中设置map和reduce的信息
for (TaskSplitMetaInfo split : splits) {
NetUtils.verifyHostnames(split.getLocations());
}
jobtracker.getInstrumentation().addWaitingMaps(getJobID(), numMapTasks);
jobtracker.getInstrumentation().addWaitingReduces(getJobID(), numReduceTasks);
this.queueMetrics.addWaitingMaps(getJobID(), numMapTasks);
this.queueMetrics.addWaitingReduces(getJobID(), numReduceTasks);
e.为splits分配map
maps = new TaskInProgress[numMapTasks];
for(int i=0; i < numMapTasks; ++i) {
inputLength += splits[i].getInputDataLength();
maps[i] = new TaskInProgress(jobId, jobFile,
splits[i],
jobtracker, conf, this, i, numSlotsPerMap);
}
LOG.info("Input size for job " + jobId + " = " + inputLength+ ". Number of splits = " + splits.length);
f.将map放入到等待执行的缓冲区内
localityWaitFactor = conf.getFloat(LOCALITY_WAIT_FACTOR, DEFAULT_LOCALITY_WAIT_FACTOR);
if (numMapTasks > 0) {
nonRunningMapCache = createCache(splits, maxLevel);
}
// set the launch time
this.launchTime = jobtracker.getClock().getTime();
g.同样的对待reduce操作
h.创建两个清除进程,一个清除map,一个清除reduce
cleanup = new TaskInProgress[2];
// cleanup map tip. This map doesn't use any splits. Just assign an empty
// split.
TaskSplitMetaInfo emptySplit = JobSplit.EMPTY_TASK_SPLIT;
cleanup[0] = new TaskInProgress(jobId, jobFile, emptySplit, jobtracker, conf, this, numMapTasks, 1);
cleanup[0].setJobCleanupTask();
// cleanup reduce tip.
cleanup[1] = new TaskInProgress(jobId, jobFile, numMapTasks,
numReduceTasks, jobtracker, conf, this, 1);
cleanup[1].setJobCleanupTask();
i.创建两个设置进程,分别对map和reduce进行初始化设置
setup = new TaskInProgress[2];
// setup map tip. This map doesn't use any split. Just assign an empty
// split.
setup[0] = new TaskInProgress(jobId, jobFile, emptySplit, jobtracker, conf, this, numMapTasks + 1, 1);
setup[0].setJobSetupTask();
// setup reduce tip.
setup[1] = new TaskInProgress(jobId, jobFile, numMapTasks,
numReduceTasks + 1, jobtracker, conf, this, 1);
setup[1].setJobSetupTask();
j.最后,设置一个互锁的方法,用来检查初始化是否成功,并写入日志
synchronized(jobInitKillStatus){
jobInitKillStatus.initDone = true;
// set this before the throw to make sure cleanup works properly
tasksInited = true;
if(jobInitKillStatus.killed) {
throw new KillInterruptedException("Job " + jobId + " killed in init");
}
}
JobHistory.JobInfo.logInited(profile.getJobID(), this.launchTime, numMapTasks, numReduceTasks);
至此,作业的初始化操作就已经完成了。由于初始化操作需要根据splits数目来确定需要初始化的map和reduce操作,因此相比较有点复杂。
3、作业的分配
作业的分配需要JobTrack和TaskTrack相互协调,JobTrack根据TaskTrack发送的心跳信息来决定任务的分配方法。心跳信息会报告当前任务的状况,会提出新的任务请求,或者提出任务执行失败的报告等。JobTrack接受到信息后会采取相应的措施。
Hadoop的作业分配是一种“拉”的方式,TaskTrack发送心跳信息(源码实现为TaskTrack类(TaskTrack.java)的transmitHeartBeat方法)给JobTrack来请求一个新的任务,而这个TaskTrack提出任务请求时也会提供自己当前map/reduce任务槽的数量供JobTrack参考。JobTrack根据心跳信息(源码实现为JobTrack类(JobTrack.java)的heartbeat方法)在本地优先的情况下对该TaskTrack分配Task。
4、作业的执行
作业的执行同样在TaskTrack中完成, 在作业执行时,第一步是将任务进行本地化,第二步是通过虚拟机执行任务。
任务本地化,基于TaskTrack.java的localizeJob方法实现,主要步骤为:
a.将job.split复制到本地;b.将job.jar复制到本地;c.将job的配置信息写入job.xml;d.创建本地目录,解压缩job.jar;e.调用launchTaskForJob方法发布任务;f.调用launchTask方法启动任务。
任务的执行包括map执行(MapTaskRunner类)和reduce执行(ReduceTaskRunner类),每个任务的执行都是通过一个JVM实现的。
5、作业的进度
作业进度的更新依赖于TaskTrack向JobTrack发送的心跳信息。每个TaskTrack都会将自己的进度信息和状态信息封装在心跳信息中,每隔5秒向JobTrack发送一次。JobTrack在收集所有的信息后统一并得出全局信息。
Hadoop随笔(一):工作流程的源码的更多相关文章
- springmvc工作原理以及源码分析(基于spring3.1.0)
springmvc是一个基于spring的web框架.本篇文章对它的工作原理以及源码进行深入分析. 一.springmvc请求处理流程 二.springmvc的工作机制 三.springmvc核心源码 ...
- Android 全面插件化 RePlugin 流程与源码解析
转自 Android 全面插件化 RePlugin 流程与源码解析 RePlugin,360开源的全面插件化框架,按照官网说的,其目的是“尽可能多的让模块变成插件”,并在很稳定的前提下,尽可能像开发普 ...
- Spark Streaming运行流程及源码解析(一)
本系列主要描述Spark Streaming的运行流程,然后对每个流程的源码分别进行解析 之前总听同事说Spark源码有多么棒,咱也不知道,就是疯狂点头.今天也来撸一下Spark源码. 对Spark的 ...
- SpringMVC执行流程及源码分析
SpringMVC流程及源码分析 前言 学了一遍SpringMVC以后,想着做一个总结,复习一下.复习写下面的总结的时候才发现,其实自己学的并不彻底.牢固.也没有学全,视频跟书本是要结合起来一起, ...
- HDFS追本溯源:HDFS操作的逻辑流程与源码解析
本文主要介绍5个典型的HDFS流程,这些流程充分体现了HDFS实体间IPC接口和stream接口之间的配合. 1. Client和NN Client到NN有大量的元数据操作,比如修改文件名,在给定目录 ...
- Android应用层View绘制流程与源码分析
1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...
- Struts流程分析+源码分析
1.初始化工作 读取配置---转换器-----读取插件 当struts-config.xml配置文件加载到内存,则会创建两个map:ActionConfigs,FromBeans.这两个map都交由M ...
- SpringMvc请求处理流程与源码探秘
流程梳理 dispatcherServlet作为前端控制器的主要作用就是接受请求与处理响应. 不过它不是传统意义上的servlet,它在接受到请求后采用转发的方式,将具体工作交给专业人士去做. 参与角 ...
- 20170908工作日记--Volley源码详解
Volley没有jar包,需要从官网上下载源码自己编译出来,或者做成相关moudle引入项目中.我们先从最简单的使用方法入手进行分析: //创建一个网络请求队列 RequestQueue reques ...
随机推荐
- Oracle 流复制实践笔记
最近因为业务需求,需要在两个数据库之间做双向实时同步,遂实践了一把Oracle的流复制,遇到了很多疑难问题,最终也貌似成功,现记录如下. 我是使用OEM来实现流复制的. 10.进行流复制的两个数据库的 ...
- lua 操作中文字符串之截取和长度竖排显示
前言 在游戏中,我们经常会遇到汉字的多行显示,比如名字竖行显示等.如下图: 为了实现上面的效果,lua实现分行是通过 \n 实现的,所以我们需要取出汉字,然后插入 \n 实现分行效果.还有一种就是 ...
- cf 700 A As Fast As Possible
题意:有$n$个小学生需要到距离为$l$的地方去,步行的速度是$v_1$,它们租了一辆大巴,速度是$v_2$,大巴上最多容纳$k$个乘客,每个小学生最多乘车一次,初始时大巴和小学生都在起点,问至少需要 ...
- 【Linux命令与工具】磁盘与目录的容量——df和du
df(disk free):列出文件系统的整体磁盘使用量 用法: df [-akmhi] [目录或文件名] 参数: -a: 列出所有的文件系统,包括系统特有的/proc等文件系统 -k: 以KB的容量 ...
- easylui datagrid 动态生成列
function load(sdate) { $.getJSON("workorder/statistics.do", { sdate : sdate+'-01' }, funct ...
- JDBC读取新插入Oracle数据库Sequence值的5种方法
Oracle的sequence实现非常灵活,所以也带来一些易用性问题,如何取到新插入记录生成的sequence值与其它数据库有较大差别,本文详国介绍了5种实现读取新插入记录sequence值的方法. ...
- Mysql新知识点150928
1.select distinct(DATE_FORMAT(updatetime,'%Y-%m')) as updatetime from barcode where pid!=0 order by ...
- hdu 4283 You Are the One 区间dp
You Are the One Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- PHP-----函数和二进制
递归-----函数本身调用本身.每一个栈中的变量都是独立的,不受外部变量的影响,除非传参.这一点和Js不一样. 在一个php页面中要引用其他的php文件可以使用require,require_once ...
- 【OOAD】OOAD概述
什么是面向对象? OOP:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构.OOP 的一条基本原则是计算机程序是由单个能够起到子程序 ...