MR切片机制及底层源码追踪

MR切片机制及底层源码追踪

一、概述

MR程序在运行的时候需要将文件切分为多个分发给不同MapTask进行处理,那么问题来了,MR底层是如何对文件进行切分的呢?

二、InputFormat切片机制

  1. Hadoop框架内部定义了一个读取数据的基类流~InputFormat(抽象类),其内部定义了2个抽象方法
    快捷键Ctrl + N 搜索InputFormat类,查看该类中方法
public abstract class InputFormat<K, V> {
	public abstract List<InputSplit> getSplits(JobContext context);	//生成切片,对数据切分
	public abstract RecordReader<K,V> createRecordReader(InputSplit 	split,TaskAttemptContext context);		//创建RecordReader对象,真正负责数据读取的对象.
	==>真实按行读取是TextInputFormat类中的RecordReader()方法		//--67行
}
  1. 我们查看InputFormat的具体实现类
    快捷键Ctrl + H 查看InputFormat类体系~~~查看具体实现类FileInputFormat(子抽象类)
  1. FileInputFormat中对getSplits()方法做出了具体实现. --389行
    createRecordReader()方法未作任何改变。 --直接定位到父类中的该方法
    isSplitable()方法:判断当前输入的数据是否可切分,默认true. --169行
    该方法具体实现是在TextInputFormat中的isSplitable()方法,主要针对的是压缩文件

在这里插入图片描述

  1. FileInputFormat是子抽象类,我们看下其具体的实现类,见 上图
    我们需要掌握的是TextInputFormat和CombineTextInputFormat
    TextInputFormat是MapReduce默认使用的读取数据的类
    比如:WordCount和Flow案例使用的均是TextInputFormat完成数据输入的
    CombineTextInputFormat用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个MapTask处理,内部改写了切片规则(用来处理小文件过多造成启动多个MapTask运行处理很小的数据量的情况)

三、切片设置

  1. 块:HDFS存储数据的单位,指把要存储到HDFS文件系统上的文件以设置好的块的大小进行物理切分成N个块(真正的将数据切割)
  2. 切片:MapReduce计算数据的单位,指把要在MR中计算的数据从逻辑上按照设置的切片大小,划分为N个切片
  • 示例:
    比如现有一个200M的文件,要读取到MR程序中做运算,假设设置切片大小为100M,那么,逻辑上是将200M的数据切为
    0-100M,100-200M两个切片,真实并没有将200M的数据进行切分,200M的文件数据依然保持完整。
    生成的切片记录的是:第一个切片文件从0位置读到100M位置 ,第二个切片从100M读取到200M的位置
    即记录读数据的时候,从什么位置读取到什么位置。
  1. 切片大小设置
    切片的大小设置并不是随意设置的,要和块的设置大小关联起来
  • 比如,现在对一个200M的文件进行操作,上传到HDFS系统,分为0-128M,128-200M两个块,那么假设存储到DN节点上就是dn1存储128M,dn2存储72M,我们希望当MR程序读取数据做运算时,MR可以一次性将节点上的数据读完或者保证一个MapTask只在一个节点上读数据(因为一个切片数据交给一个MapTask处理),所以综合考虑,我们应该将切片大小设置成和块大小一致。
  • 默认:将切片大小设置成==block块大小,避免MR程序读取数据时,出现跨节点读取。

四、切片源码解读

//1、getSplits()方法源码解析:
/*
getFormatMinSplitSize() :1
getMinSplitSize(job) :
	如果设置mapreduce.input.fileinputformat.split.minsize那么就使用设置的值,如果没有设置
	默认是1
*/
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));

/*
如果在Configuration中配置了如下内容的值
	mapreduce.input.fileinputformat.split.maxsize那么获取的就是配置的值。
	如果没有配置那么默认值是Long.MAX_VALUE
*/
long maxSize = getMaxSplitSize(job);		

//最终将生成的切片放入到splits集合中. 
List<InputSplit> splits = new ArrayList<InputSplit>();		
//获取存储文件的详情		
List<FileStatus> files = listStatus(job); 
//遍历files		
for (FileStatus file: files) {	
	  //判断file是否是文件夹				
      if (ignoreDirs && file.isDirectory()) {	
        continue;
      }
//获取文件路径
Path path = file.getPath();	
//获取文件长度					
long length = file.getLen();	
//判断文件是否可切分				
if (isSplitable(job, path)) {	
	//获取文件的块大小				
	long blockSize = file.getBlockSize();     
	//如果是集群环境,获取到的就是集群中设置的块大小,如果是本地环境,本地默认的块大小32M (33554432)
	//计算切片大小
	long splitSize = computeSplitSize(blockSize, minSize, maxSize);		
	
		//查看computeSplitSize()方法
		protected long computeSplitSize(long blockSize, long minSize,long maxSize) {
				/*
					默认块大小=切片大小
					设置:
						切片大小 < 块大小:修改maxSize的值
						切片大小 > 块大小:修改minSize的值
				*/
				return Math.max(minSize, Math.min(maxSize, blockSize));
			}
}

//文件的剩余大小
long bytesRemaining = length;					
//只有当剩余文件大小 / 切片大下 其值 > 1.1 时,才会进行切片操作
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {		
	//计算切片时如何去切
   int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
   /*
		splits :用来存储切片信息的集合
		makeSplit : 用来生成切片信息
		length-bytesRemaining : 切片的起始位置
		splitSize :切片的偏移量(长度)
	*/
   splits.add(makeSplit(path, length-bytesRemaining, splitSize,
   		  blkLocations[blkIndex].getHosts(),			//记录的是读取那个文件
          blkLocations[blkIndex].getCachedHosts()));	//从文件那个位置读取到那个位置
          //重新计算剩余文件的大小
   		  bytesRemaining -= splitSize;	
}


//把剩余文件大小/splitSize <= 1.1 把满足此条件的剩余文件切成一片 
if (bytesRemaining != 0) {
	int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
	/*
		splits :用来存储切片信息的集合
		makeSplit : 用来生成切片信息
			length-bytesRemaining : 切片的起始位置
			bytesRemaining :切片的偏移量(长度)
	*/
	splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
			   blkLocations[blkIndex].getHosts(),
			   blkLocations[blkIndex].getCachedHosts()));
}

 

结论:

  1. 如果剩余待切片的数据大小 除以 块大小,大于1.1 ,才会继续切片,如果不大于,则直接将剩余的数据 生成一个切片.
  2. 真实切片时,是完全按照块大小进行切的,只是针对剩余数据是否仍要切片时,会有一个大于1.1的判断,要做上述判断,避免生成很小的切片,节省MapTask资源
  3. 切片大小默认等于Block大小128M,避免MR程序读取数据时,出现跨节点读取,节省资源。
  4. 每个切片都需要由一个MapTask来处理 , 也就意味着在一个MR中,有多少个切片,就会有多少个MapTask。
  5. 切片的时候每个文件单独切片,不会整体切片.(完全基于单个文件)
  6. 切片的个数不是越多越好,也不是越少越好,按照实际情况,处理成合适的切片数.

五、CombineTextInputFormat~切片机制

CombineTextInputFormat解决的问题:
用于小文件过多的场景,它可以将多个小文件从逻辑上规划到一个切片中,这样,多个小文件就可以交给一个MapTask处理。

核心操作:设置虚拟切片最大值
在Driver中设置
job.setInputFormatClass(CombineTextInputFormat.class); --不设置,默认是FileInputFormat
CombineTextInputFormat.setMaxInputSplitSize(job, 4194304);// 假设设置为4M

1、如果文件的大小小于MaxInputSplitSize , 则文件规划成一个
2、如果文件的大小 大于MaxInputSplitSize ,但是小于 MaxInputSplitSize2 , 则文件规划成两个(对半分)
3、如果文件的大小大于 MaxInputSplitSize
2 ,先按照MaxInputSplitSize的大小规划一个, 剩余的再进行规划.
4、最终按照 MaxInputSplitSize 大小来生成切片,
5、将规划好的每个虚拟文件逐个累加,只要不超过 MaxInputSplitSize大小,则都是规划到一个切片中的。

举例:
有4个小文件大小分别为1.7M、5.1M、3.4M以及6.8M这四个小文件
则虚拟存储之后形成6个文件块,大小分别为:1.7M,(2.55M、2.55M),3.4M以及(3.4M、3.4M)
基于虚拟存储,最终会形成3个切片,大小分别为:(1.7+2.55)M,(2.55+3.4)M,(3.4+3.4)M

基础链接:
MapReduce核心思想: https://blog.csdn.net/weixin_42796403/article/details/109732859
手写MR程序之wordcount案例: https://blog.csdn.net/weixin_42796403/article/details/109749080
Hadoop切片与MapTask并行度决定机制: https://blog.csdn.net/weixin_42796403/article/details/109753044
MR核心编程思想总结: https://blog.csdn.net/weixin_42796403/article/details/109802287
FileInputFormat切片源码解析: https://blog.csdn.net/weixin_42796403/article/details/109804581
CombineTextInputFormat切片机制: https://blog.csdn.net/weixin_42796403/article/details/109806713
TextInputFormat切片机制: https://blog.csdn.net/weixin_42796403/article/details/109811157

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值