mapreduce job提交到yarn分析

本文详细介绍了Hadoop MapReduce作业提交到YARN的过程,包括配置作业、新建Job、启动作业、提交作业的步骤。在提交过程中,重点解析了JobSubmitter如何创建切片,通过TextInputFormat的getSplit方法计算切片大小,并根据文件块信息进行逻辑切片。此外,还提到了设置最小和最大切片大小的配置参数以及staging目录文件的复制设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

mapreduce job提交到yarn分析

相关类

Configuration

配置job,如果不配置则使用默认配置。

Job

封装了一个job运行的信息。

Cluster

代表本地连接ResourceManager和文件系统的一个对象;内部封装了JobRunner和运行时的文件系统信息;如果是本地模式则JobRunner为localJobRunner,和本地文件系统,集群模式为YarnRunner和hdfs。

ClientProtocolProvider

用于生成JobRunner;如果是本地模式则ClientProtocolProvider为LocalClientProtocolProvider,集群模式为YarnClientProtocolProvider。

ServiceLoader

用来加载ClientProtocolProvider。

InputFormat

对作业进行逻辑切片。

JobSubmitter

提交一个job;包括获取jobId,提交目录路径,上传作业运行的文件(jar,切片信息文件,job.xml)。

提交过程

在这里插入图片描述

新建作业
  • 新建Configuration对象,通过该对象的set()方法,设置作业运行的配置参数和连接hadoop集群的所需参数。

  • 新建Job对象,将上述Configuration对象传入Job对象中。

  • 设置Job运行时参数,jar本地位置,map和reduce全类名,输入输出路径等等。

作业启动
  • 通过调用Job对象的Job#waitForCompletion()或者Job#submit()方法启动作业。
Job#submit
  • 首先确认作业状态为DEFINE;然后调用setUseNewAPI()判断是否使用新API,默认使用新的API。

  • 调用Job#connect()连接集群;首先判断Cluster对象是否存在,如果不存在则最终调用Cluster#initialize()创建一个Cluster对象代表本地和集群的连接。

  • 创建一个JobSubmitter对象用于提交作业。

  • 使用上述JobSubmitter对象调用JobSubmitter#submitJobInternal()方法提交作业到集群。

Cluster#initialize()
  • 通过foreach语句,使用ServiceLoader加载ClientProtocolProvider;首先ServiceLoader会创建一个LocalClientProtocolProvider对象。

  • 调用ClientProtocolProvider#create()方法准备生成JobRunner对象.

  • 判断是否Configuration对象中设置的mapreduce.framework.name是否为yarn,如果没有设置则该LocalClientProtocolProvider对象直接生成LocalJobRunner对象并返回;如果设置了,则返回null并进入下一次foreach。

  • 第二次运行forench将会返回YarnClientProtocolProvider,调用ClientProtocolProvider#create()方法返回YarnRunner对象并且连接ResourceManager。

JobSubmitter#submitJobInternal()
  • 首先会检查输出路径是否满足要求;判断参数mapreduce.application.framework.path是否配置(该参数见官方文档)。
  • 向Yarn获取一个jobId,生成一个提交目录的路径;将job的jar文件和该文件的crc校验文件提交到提交目录。
  • 调用JobSubmitter#writeSplits()方法,如果使用newAPI最终调用JobSubmitter#writeNewSplits(),向提交目录写入切片信息文件。
  • 上述函数继续调用InputFormat#getSplits()获取切片信息,因为默认的InputFormat对象是TextInputFormat。所以下面分析TextInputFormat的getSplit()方法;TextInputFormat继承自FileInputFormat。
  • 获取到切片信息后,会对切片按照size大小进行降序排列。
  • 写出切片信息文件和job.xml到提交目录。
  • 提交作业。
FileInputFormat#getSplit()
  • 获取最小切片和最大切片大小(单位B),然后获取输入目录下的所有文件的信息。切片只是逻辑切片,通过切片文件保存关于源文件的分割信息,而不是直接对源文件进行切分。

  • 遍历输入文件:

  1. 获取文件在对应文件系统的块信息。

  2. 调用TextInputFormat判断当前配置是否支持切片,如果未设置压缩器则支持切片,如果设置了,则判断该压缩器是否是 SplittableCompressionCodec的子类,是则可切片。

  3. 如果不可切片,将整个文件构造一个大的逻辑切片返回。

  4. 如果可以切片,则获取该文件系统的文件块大小;更据(文件块大小,设置的最小切片size,最大切片size)计算切片大小,计算公式 Math.max(minSize, Math.min(maxSize, blockSize))。

  5. 循环进行切片,每次都判断剩余部分除以切片size是否大于1.1,大于则循环继续;否则直接将剩余部分添加到切片信息中。

部分代码

Job#submit()
//确认job状态
ensureState(JobState.DEFINE);
//设置api
setUseNewAPI();
//--------------------------------连接集群----------------------------------------
connect();
//getClient()返回的是JobRunner对象
JobSubmitter submitter = 
      getJobSubmitter(cluster.getFileSystem(), cluster.getClient());
status = ugi.doAs(new PrivilegedExceptionAction<JobStatus>() {
    public JobStatus run() throws IOException, InterruptedException, 
    ClassNotFoundException {
        //----------------------------提交job-----------------------------------------
      return submitter.submitJobInternal(Job.this, cluster);
    }
  });
//将job状态设置为运行
state = JobState.RUNNING;
Cluster#initialize()
//ClientProtocolProvider
for (ClientProtocolProvider provider : frameworkLoader) {
    //本地模式返回LocalJobRunner
    //集群模式返回YarnRunner
    clientProtocol = provider.create(conf);
}
JobSubmitter#submitJobInternal()
//添加MapReduce框架存档的路径到conf中
addMRFrameworkToDistributedCache(conf);
//获取jobId
JobID jobId = submitClient.getNewJobID();
//从得到一个job提交的目录。
Path submitJobDir = new Path(jobStagingArea, jobId.toString());
//将job的jar文件和该文件的crc校验文件提交到提交目录。
copyAndConfigureFiles(job, submitJobDir);
//切片并写出切片信息文件到提交目录
int maps = writeSplits(job, submitJobDir);
//写出job.xml到提交目录
writeConf(conf, submitJobFile);
JobSubmitter#writeNewSplits()
//获取配置的InputFormat对象,默认为TextInputFormat
InputFormat<?, ?> input =
  ReflectionUtils.newInstance(job.getInputFormatClass(), conf);
//获取切片信息。
List<InputSplit> splits = input.getSplits(job)
// sort the splits into order based on size, so that the biggest
// go first
Arrays.sort(array, new SplitComparator());
//将切片信息写出到提交目录
JobSplitWriter.createSplitFiles(jobSubmitDir, conf, 
				jobSubmitDir.getFileSystem(conf), array);
FileInputFormat#getSplit()
//----------------------------------------切片大小,单位是字节(B)----------------------------------------
long minSize = Math.max(getFormatMinSplitSize()/*long(1)*/, getMinSplitSize(job)/*job conf中可以设置最小切片大小,默认long(1)*/);
//job conf中可以设置最大切片大小,默认long型最大值
long maxSize = getMaxSplitSize(job);
//获取输入目录下的所有文件的信息。
List<FileStatus> files = listStatus(job);
//遍历文件
for (FileStatus file: files) {
    	//获取文件所在的块信息
		if (file instanceof LocatedFileStatus) {
          //从本地文件系统获取
          blkLocations = ((LocatedFileStatus) file).getBlockLocations();
        } else {
          //从hdfs获取
          FileSystem fs = path.getFileSystem(job.getConfiguration());
          blkLocations = fs.getFileBlockLocations(file, 0, length);
        }
    	//如果当前配置环境支持切片
    	if (isSplitable(job, path)) {
            //获取块大小
			long blockSize = file.getBlockSize();
            //使用公式Math.max(minSize, Math.min(maxSize, blockSize))计算切片大小。
			long splitSize = computeSplitSize(blockSize, minSize, maxSize);
			long bytesRemaining = length;
            //写入切片信息
            //写入时需要判断bytesRemaining/splitSize > SPLIT_SLOP
         	 while (((double) bytesRemaining)/splitSize > SPLIT_SLOP/*1.1*/) {
            	int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            	splits.add(makeSplit(path, length-bytesRemaining, splitSize,
                        blkLocations[blkIndex].getHosts(),
                        blkLocations[blkIndex].getCachedHosts()));
           		 bytesRemaining -= splitSize;
          	}
            //写入切片信息
          	if (bytesRemaining != 0) {
            int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
            	splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
            	blkLocations[blkIndex].getHosts(),
            	blkLocations[blkIndex].getCachedHosts()));
          } 
        }
    	//如果当前环境配置不支持切片,直接将整个文件作为一个切片。
       else { // not splitable
          splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
                      blkLocations[0].getCachedHosts()));
       }
TextInputFormat#isSplitable()
//根据文件后缀名获取对应的压缩器。该压缩器是map端输入压缩器。
final CompressionCodec codec =
  new CompressionCodecFactory(context.getConfiguration()).getCodec(file);

if (null == codec) {
  return true;
}
//返回该压缩器是否是SplittableCompressionCodec的子类,是则可以切片。
return codec instanceof SplittableCompressionCodec;

注意点总结

设置job切片大小的最小值和最大值
  1. 通过配置mapreduce.input.fileinputformat.split.minsize设置最小值,默认为1;mapreduce.input.fileinputformat.split.maxsize设置最大值,默认为long型最大值。
切片
  1. 切片只是逻辑切片,通过切片文件保存关于源文件的分割信息,而不是直接对源文件进行切分。
  2. 需要调用InputFormat#isSplitable()方法判断当前配置环境是否可以切片,在TextInputFormat#isSplitable()中,如果要能够切片,压缩器(该压缩器是map输入端压缩器)需要是SplittableCompressionCodec的子类;或者没有配置压缩器。
  3. 计算切片大小的公式为 Math.max(minSize, Math.min(maxSize, blockSize))。所以要调高splitSize需要调高minSize值>blockSize,要调低splitSize需要调低maxSize值<blockSize。
  4. 默认splitSize==blockSize。
  5. 循环切片:每次切片前当前文件剩余未处理大小除以splitSize大于1.1则对剩余部分进行切片(注意:虽然使用1.1而不是1进行判断,但是切片大小还是splitSize而不是splitSize*1.1),否则就直接将其添加到切片信息中。
  6. 会对切片按照size大小,降序排列。
staging目录文件的replication
  1. job.jar和job.split的replication等于参数mapreduce.client.submit.file.replication配置值;如果不配置该参数则默认时10.
  2. job.xml为hdfs设置的replication,默认是3

参考

http://ercoppa.github.io/HadoopInternals/JobSubmitter.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值