Hadoop-Mapreduce(二)

InputFormat数据输入

  • Job提交流程和切片源码详解
waitForCompletion()
submit();
// 1建立连接
	connect();	
		// 1)创建提交job的代理
		new Cluster(getConfiguration());
			// (1)判断是本地yarn还是远程
			initialize(jobTrackAddr, conf); 
	// 2 提交job
submitter.submitJobInternal(Job.this, cluster)
	// 1)创建给集群提交数据的Stag路径
	Path jobStagingArea = JobSubmissionFiles.getStagingDir(cluster, conf);
	// 2)获取jobid ,并创建job路径
	JobID jobId = submitClient.getNewJobID();
	// 3)拷贝jar包到集群
copyAndConfigureFiles(job, submitJobDir);	
	rUploader.uploadFiles(job, jobSubmitDir);
// 4)计算切片,生成切片规划文件
writeSplits(job, submitJobDir);
	maps = writeNewSplits(job, jobSubmitDir);
		input.getSplits(job);
// 5)向Stag路径写xml配置文件
writeConf(conf, submitJobFile);
	conf.writeXml(out);
// 6)提交job,返回提交状态
status = submitClient.submitJob(jobId, submitJobDir.toString(), job.getCredentials());
    • FileInputFormat源码解析(input.getSplits(job))
    • 找到你数据存储的目录。
    • 开始遍历处理(规划切片)目录下的每一个文件
    • 遍历第一个文件ss.txt
      • 获取文件大小fs.sizeOf(ss.txt);
      • 计算切片大小computeSliteSize(Math.max(minSize,Math.min(maxSize,blocksize)))=blocksize=128M
      • 默认情况下,切片大小=blocksize
      • 开始切,形成第1个切片:ss.txt—0:128M 第2个切片ss.txt—128:256M 第3个切片ss.txt—256M:300M(每次切片时,都要判断切完剩下的部分是否大于块的1.1倍,不大于1.1倍就划分一块切片)
      • 将切片信息写到一个切片规划文件中
      • 整个切片的核心过程在getSplit()方法中完成。
      • 数据切片只是在逻辑上对输入数据进行分片,并不会再磁盘上将其切分成分片进行存储。InputSplit只记录了分片的元数据信息,比如起始位置、长度以及所在的节点列表等。
      • 注意:block是HDFS物理上存储的数据,切片是对数据逻辑上的划分。
    • 提交切片规划文件到yarn上,yarn上的MrAppMaster就可以根据切片规划文件计算开启maptask个数。
  • FileInputFormat切片机制

    • FileInputFormat中默认的切片机制:

      • 简单地按照文件的内容长度进行切片

      • 切片大小,默认等于block大小

      • 切片时不考虑数据集整体,而是逐个针对每一个文件单独切片

        比如待处理数据有两个文件:

        file1.txt 320M

        file2.txt 10M

      • 经过FileInputFormat的切片机制运算后,形成的切片信息如下:

        file1.txt.split1-- 0~128

        file1.txt.split2-- 128~256

        file1.txt.split3-- 256~320

        file2.txt.split1-- 0~10M

    • FileInputFormat切片大小的参数配置

      • 通过分析源码,在FileInputFormat的280行中,计算切片大小的逻辑:Math.max(minSize, Math.min(maxSize, blockSize));

      • 切片主要由这几个值来运算决定

      • mapreduce.input.fileinputformat.split.minsize=1 默认值为1

      • mapreduce.input.fileinputformat.split.maxsize= Long.MAXValue 默认值Long.MAXValue

        因此,默认情况下,切片大小=blocksize。

      • maxsize(切片最大值):参数如果调得比blocksize小,则会让切片变小,而且就等于配置的这个参数的值。

      • minsize(切片最小值):参数调的比blockSize大,则可以让切片变得比blocksize还大。

    • 获取切片信息API

// 根据文件类型获取切片信息
FileSplit inputSplit = (FileSplit) context.getInputSplit();
// 获取切片的文件名称
String name = inputSplit.getPath().getName();

CombineTextInputFormat切片机制

  • 关于大量小文件的优化策略

    • 默认情况下TextInputformat对任务的切片机制是按文件规划切片,不管文件多小,都会是一个单独的切片,都会交给一个maptask,这样如果有大量小文件,就会产生大量的maptask,处理效率极其低下。

    • 优化策略

      • 最好的办法,在数据处理系统的最前端(预处理/采集),将小文件先合并成大文件,再上传到HDFS做后续分析。

      • 补救措施:如果已经是大量小文件在HDFS中了,可以使用另一种InputFormat来做切片(CombineTextInputFormat),它的切片逻辑跟TextFileInputFormat不同:它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个maptask。

      • 优先满足最小切片大小,不超过最大切片大小

        CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m

        CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m

        举例:0.5m+1m+0.3m+5m=2m + 4.8m=2m + 4m + 0.8m

    • 具体实现步骤

//  如果不设置InputFormat,它默认用的是TextInputFormat.class
job.setInputFormatClass(CombineTextInputFormat.class)
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 4m
CombineTextInputFormat.setMinInputSplitSize(job, 2097152);// 2m
  • 案例实操

  • InputFormat接口实现类

    MapReduce任务的输入文件一般是存储在HDFS里面。输入的文件格式包括:基于行的日志文件、二进制格式文件等。这些文件一般会很大,达到数十GB,甚至更大。那么MapReduce是如何读取这些数据的呢?下面我们首先学习InputFormat接口。

    InputFormat常见的接口实现类包括:TextInputFormat、KeyValueTextInputFormat、NLineInputFormat、CombineTextInputFormat和自定义InputFormat等。

  • TextInputFormat

    TextInputFormat是默认的InputFormat。每条记录是一行输入。键K是LongWritable类型,存储该行在整个文件中的字节偏移量。值是这行的内容,不包括任何行终止符(换行符和回车符)。

    以下是一个示例,比如,一个分片包含了如下4条文本记录。

Rich learning form
Intelligent learning engine
Learning more convenient
From the real demand for more close to the enterprise

每条记录表示为以下键/值对:

(0,Rich learning form)
(19,Intelligent learning engine)
(47,Learning more convenient)
(72,From the real demand for more close to the enterprise)
  • 很明显,键并不是行号。一般情况下,很难取得行号,因为文件按字节而不是按行切分为分片。

  • KeyValueTextInputFormat

    每一行均为一条记录,被分隔符分割为key,value。可以通过在驱动类中设置conf.set(KeyValueLineRecordReader.KEY_VALUE_SEPERATOR, " ");来设定分隔符。默认分隔符是tab(\t)。

    以下是一个示例,输入是一个包含4条记录的分片。其中——>表示一个(水平方向的)制表符。

line1 ——>Rich learning form

line2 ——>Intelligent learning engine

line3 ——>Learning more convenient

line4 ——>From the real demand for more close to the enterprise

每条记录表示为以下键/值对:

(line1,Rich learning form)
(line2,Intelligent learning engine)
(line3,Learning more convenient)
(line4,From the real demand for more close to the enterprise)
  • 此时的键是每行排在制表符之前的Text序列。

  • NLineInputFormat

    如果使用NlineInputFormat,代表每个map进程处理的InputSplit不再按block块去划分,而是按NlineInputFormat指定的行数N来划分。即输入文件的总行数/N=切片数(20),如果不整除,切片数=商+1。

    以下是一个示例,仍然以上面的4行输入为例。

Rich learning form
Intelligent learning engine
Learning more convenient
From the real demand for more close to the enterprise

例如,如果N是2,则每个输入分片包含两行。开启2个maptask。

(0,Rich learning form)
(19,Intelligent learning engine)

另一个 mapper 则收到后两行:

(47,Learning more convenient)
(72,From the real demand for more close to the enterprise)
  • 这里的键和值与TextInputFormat生成的一样。

  • 自定义InputFormat

    • 概述

      • 自定义一个类继承FileInputFormat。
      • 改写RecordReader,实现一次读取一个完整文件封装为KV。
      • 在输出时使用SequenceFileOutPutFormat输出合并文件。
    • 案例实操

      详见7.4小文件处理(自定义InputFormat)。

MapTask工作机制

  • 并行度决定机制

    • 问题引出

      maptask的并行度决定map阶段的任务处理并发度,进而影响到整个job的处理速度。那么,mapTask并行任务是否越多越好呢?

    • MapTask并行度决定机制

      一个job的map阶段MapTask并行度(个数),由客户端提交job时的切片个数决定。
      在这里插入图片描述MapTask工作机制
      在这里插入图片描述

(1)Read阶段:Map Task通过用户编写的RecordReader,从输入InputSplit中解析出一个个key/value。

(2)Map阶段:该节点主要是将解析出的key/value交给用户编写map()函数处理,并产生一系列新的key/value。

(3)Collect收集阶段:在用户编写map()函数中,当数据处理完成后,一般会调用OutputCollector.collect()输出结果。在该函数内部,它会将生成的key/value分区(调用Partitioner),并写入一个环形内存缓冲区中。

(4)Spill阶段:即“溢写”,当环形缓冲区满后,MapReduce会将数据写到本地磁盘上,生成一个临时文件。需要注意的是,将数据写入本地磁盘之前,先要对数据进行一次本地排序,并在必要时对数据进行合并、压缩等操作。
	溢写阶段详情:
	步骤1:利用快速排序算法对缓存区内的数据进行排序,排序方式是,先按照分区编号partition进行排序,然后按照key进行排序。这样,经过排序后,数据以分区为单位聚集在一起,且同一分区内所有数据按照key有序。
	步骤2:按照分区编号由小到大依次将每个分区中的数据写入任务工作目录下的临时文件output/spillN.out(N表示当前溢写次数)中。如果用户设置了Combiner,则写入文件之前,对每个分区中的数据进行一次聚集操作。
	步骤3:将分区数据的元信息写到内存索引数据结构SpillRecord中,其中每个分区的元信息包括在临时文件中的偏移量、压缩前数据大小和压缩后数据大小。如果当前内存索引大小超过1MB,则将内存索引写到文件output/spillN.out.index中。

(5)Combine阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
	当所有数据处理完后,MapTask会将所有临时文件合并成一个大文件,并保存到文件output/file.out中,同时生成相应的索引文件output/file.out.index。
	在进行文件合并过程中,MapTask以分区为单位进行合并。对于某个分区,它将采用多轮递归合并的方式。每轮合并io.sort.factor(默认100)个文件,并将产生的文件重新加入待合并列表中,对文件排序后,重复以上过程,直到最终得到一个大文件。
	让每个MapTask最终只生成一个数据文件,可避免同时打开大量文件和同时读取大量小文件产生的随机读取带来的开销。

猜你喜欢

转载自blog.csdn.net/qq_45092505/article/details/105371512