Hadoop的数据输入的源码解析
我们知道,任何一个工程项目,最重要的是三个部分:输入,中间处理,输出。今天我们来深入的了解一下我们熟知的Hadoop系统中,输入是如何输入的?
在hadoop中,输入数据都是通过对应的InputFormat类和RecordReader类来实现的,其中InputFormat来实现将对应输入文件进行分片,RecordReader类将对应分片中的数据读取进来。具体的方式如下:
(1)InputFormat类是一个接口。
public interface InputFormat<K, V> {
InputSplit[] getSplits(JobConf job, int numSplits) throws IOException;
RecordReader<K, V> getRecordReader(InputSplit split,
JobConf job,
Reporter reporter) throws IOException;
}
(2)FileInputFormat类实现了InputFormat接口。该类实现了getSplits方法,但是它也没有实现对应的getRecordReader方法。也就是说FileInputFormat还是一个抽象类。这里需要说明的一个问题是,FileInputFormat用isSplitable方法来指定对应的文件是否支持数据的切分。默认情况下都是支持的,一般子类都需要重新实现它。
public abstract class FileInputFormat<K, V> implements InputFormat<K, V> {
public InputSplit[] getSplits(JobConf job, int numSplits) throws IOException {
FileStatus[] files = listStatus(job);
// Save the number of input files in the job-conf
job.setLong(NUM_INPUT_FILES, files.length);
long totalSize = 0; // compute total size
for (FileStatus file: files) { // check we have valid files
if (file.isDir()) {
throw new IOException("Not a file: "+ file.getPath());
}
totalSize += file.getLen();
}
long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
long minSize = Math.max(job.getLong("mapred.min.split.size", 1),
minSplitSize);
// generate splits
ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
NetworkTopology clusterMap = new NetworkTopology();
for (FileStatus file: files) {
Path path = file.getPath();
FileSystem fs = path.getFileSystem(job);
long length = file.getLen();
BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);
if ((length != 0) && isSplitable(fs, path)) {
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
long bytesRemaining = length;
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
String[] splitHosts = getSplitHosts(blkLocations,
length-bytesRemaining, splitSize, clusterMap);
splits.add(new FileSplit(path, length-bytesRemaining, splitSize,
splitHosts));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) {
splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkLocations.length-1].getHosts()));
}
} else if (length != 0) {
String[] splitHosts = getSplitHosts(blkLocations,0,length,clusterMap);
splits.add(new FileSplit(path, 0, length, splitHosts));
} else {
//Create empty hosts array for zero length files
splits.add(new FileSplit(path, 0, length, new String[0]));
}
}
LOG.debug("Total # of splits: " + splits.size());
return splits.toArray(new FileSplit[splits.size()]);
}
//该方法是用来判断是否可以进行数据的切分
protected boolean isSplitable(FileSystem fs, Path filename) {
return true;
}
//但是它也没有实现对应的getRecordReader方法。也就是说FileInputFormat还是一个抽象类。
public abstract RecordReader<K, V> getRecordReader(InputSplit split,
JobConf job,
Reporter reporter) throws IOException;
}
(3)TextFileInputFormat类仅仅实现了FileInputFormat类的getRecordReader方法,并且重写了isSplitable方法,他并没有实现getSplits方法,由此可知,他的getSplits的实现还是交由父类FileInputFormat来实现的。(这里需要注意TextFileInputFormat并不是InputFormat的子类,TextFileInputFormat它仅仅是继承了InputFormat的getRecordReader的方法而已。)
public class TextInputFormat extends FileInputFormat<LongWritable, Text>
implements JobConfigurable {
private CompressionCodecFactory compressionCodecs = null;
public void configure(JobConf conf) {
compressionCodecs = new CompressionCodecFactory(conf);
}
//子类重新实现了isSplitable方法
protected boolean isSplitable(FileSystem fs, Path file) {
final CompressionCodec codec = compressionCodecs.getCodec(file);
if (null == codec) {
return true;
}
return codec instanceof SplittableCompressionCodec;
}
//该方法实现了将文件中的数据读入到对应的Map方法中。
public RecordReader<LongWritable, Text> getRecordReader(
InputSplit genericSplit, JobConf job,
Reporter reporter)
throws IOException {
reporter.setStatus(genericSplit.toString());
String delimiter = job.get("textinputformat.record.delimiter");
byte[] recordDelimiterBytes = null;
if (null != delimiter) recordDelimiterBytes = delimiter.getBytes();
return new LineRecordReader(job, (FileSplit) genericSplit,
recordDelimiterBytes);
}
}
从上面可以看出一个Text格式的文件是通过什么样的类继承层次输入到map方法中。下面主要介绍一下,到底是如何切分的?我们从类的继承层次关系上可以看出,具体的切分方式是通过FileInputFormat类来实现的。因此,要了解文件是如何切分的,只需要查看一下FileInputFormat类中的getSplits方法的实现细节即可。下面我再次把FileInputFormat类中的getSplits方法贴出来:然后分析每一句代码。
public InputSplit[] getSplits(JobConf job, int numSplits)
throws IOException {
FileStatus[] files = listStatus(job); //列出当前job中所有的输入文件
// Save the number of input files in the job-conf
job.setLong(NUM_INPUT_FILES, files.length); //设置当前job的输入文件数目
//计算当前job所有输入文件总的大小
long totalSize = 0; // compute total size
//遍历每一个文件
for (FileStatus file: files) { // check we have valid files
if (file.isDir()) {
throw new IOException("Not a file: "+ file.getPath());
}
totalSize += file.getLen();
}
// numSplits是分片数,goalSize是平均每一个分片的大小,minSize是每个分片最小值
long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
long minSize = Math.max(job.getLong("mapred.min.split.size", 1),
minSplitSize);
// generate splits 计算分片
ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
NetworkTopology clusterMap = new NetworkTopology();
for (FileStatus file: files) {
Path path = file.getPath();
FileSystem fs = path.getFileSystem(job);
long length = file.getLen();
//获取文件的位置
BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);
//isSplitable方法根据对应文件名称判断对应文件是否可以切分
if ((length != 0) && isSplitable(fs, path)) {
long blockSize = file.getBlockSize();//获取文件块的大小
// computeSplitSize方法计算真正的分片大小
long splitSize = computeSplitSize(goalSize, minSize, blockSize);
long bytesRemaining = length;//文件剩余大小
// SPLIT_SLOP=1.1,文件大小/分片的大小> SPLIT_SLOP则进行切分。
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
// splitHosts用来记录分片元数据信息(包括切片的位置,大小等等)
String[] splitHosts = getSplitHosts(blkLocations,
length-bytesRemaining, splitSize, clusterMap);
splits.add(new FileSplit(path, length-bytesRemaining, splitSize,
splitHosts));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) {
splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkLocations.length-1].getHosts()));
}
} else if (length != 0) { //如果文件不能切分,相应的会将整个文件作为一个分片。
String[] splitHosts = getSplitHosts(blkLocations,0,length,clusterMap);
splits.add(new FileSplit(path, 0, length, splitHosts));
} else {
//Create empty hosts array for zero length files
splits.add(new FileSplit(path, 0, length, new String[0]));
}
}
LOG.debug("Total # of splits: " + splits.size());
return splits.toArray(new FileSplit[splits.size()]);
}
//真正计算分片大小的地方。
protected long computeSplitSize(long goalSize, long minSize,
long blockSize) {
return Math.max(minSize, Math.min(goalSize, blockSize));
}
综上所述,对于MR的输入文件采用的方式是通过FileInputFormat类来进行数据的切分,在切分之前,是通过isSplitable方法来判断是否可以切分,若不能切分,则会将整个文件作为一个分片作为输入。因此,若有业务需求需要对应文件不能进行切分的话,可以将isSplitable方法方位false即可。
这里还需要注意一个问题,倘若你的文件都是小文件的话,对应的getSplits方法也不会对其进行切分的。一般情况小文件指的是其大小小于对应hadoop中HDFS的块的大小(128M)。
Hadoop的数据输入的源码解析的更多相关文章
- 【大数据】深入源码解析Map Reduce的架构
这几天学习了MapReduce,我参照资料,自己又画了两张MapReduce的架构图. 这里我根据架构图以及对应的源码,来解释一次分布式MapReduce的计算到底是怎么工作的. 话不多说,开始! ...
- Java ThreadLocal 的使用与源码解析
GitHub Page: http://blog.cloudli.top/posts/Java-ThreadLocal-的使用与源码解析/ ThreadLocal 主要解决的是每个线程绑定自己的值,可 ...
- 谷歌BERT预训练源码解析(一):训练数据生成
目录预训练源码结构简介输入输出源码解析参数主函数创建训练实例下一句预测&实例生成随机遮蔽输出结果一览预训练源码结构简介关于BERT,简单来说,它是一个基于Transformer架构,结合遮蔽词 ...
- [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader
[源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader 目录 [源码解析] PyTorch 分布式(2) --- 数据加载之DataLoader 0x00 摘要 0x01 ...
- [源码解析] PyTorch 流水线并行实现 (3)--切分数据和运行时系统
[源码解析] PyTorch 流水线并行实现 (3)--切分数据和运行时系统 目录 [源码解析] PyTorch 流水线并行实现 (3)--切分数据和运行时系统 0x00 摘要 0x01 分割小批次 ...
- HDFS源码解析:教你用HDFS客户端写数据
摘要:终于开始了这个很感兴趣但是一直觉得困难重重的源码解析工作,也算是一个好的开端. 本文分享自华为云社区<hdfs源码解析之客户端写数据>,作者: dayu_dls. 在我们客户端写数据 ...
- Fresco源码解析 - DataSource怎样存储数据
Fresco源码解析 - DataSource怎样存储数据 datasource是一个独立的 package,与FB导入的guava包都在同一个工程内 - fbcore. datasource的类关系 ...
- [源码]解析 SynchronousQueue 上界,下界.. 数据保存和数据传递. 堵塞队列. 有无频繁await?
简析SynchronousQueue.LinkedBlockingQueue(两个locker,更快),ArrayBlockingQueue(一个locker,读写都竞争) 三者都是bloc ...
- Vue源码解析---数据的双向绑定
本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...
随机推荐
- 百川即时通讯跨appkey问题解决
当前调用的sdk版本为: <script src="https://g.alicdn.com/aliww/??h5.openim.sdk/1.0.6/scripts/wsdk.js,h ...
- adb命令学习
录制屏幕操作Android4.4版本以上支持录制屏幕 adb shell screenrecord /sdcard/demo.mp4 ADB logcat 输出时间信息: adb logcat -v ...
- centos6.5安装lnmp环境
1.安装nignx的源,默认cenots6没有的. rpm -ivh http://nginx.org/packages/centos/6/noarch/RPMS/nginx-release-cent ...
- x-csrf-token
- AS3和js相互通信要点分析
目标:在html页面里可以使用事件来影响到swf文件的内容,swf文件也可以影响html里js代码的内容 一.新建flash文件,用Flash CC试用版新建一个TextArea.fla的源文件,不添 ...
- Web之路笔记之四
2014秋季学期Web2.0课程作业 <Homework1 - Recipe> 给出内容的文本文档,根据要求编写html和css.基本上没有难点. 1. 需要添加标签栏名称前面的小图标,是 ...
- html中给表格添加斜线
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- TcpListener 类
构造函数 名称 说明 TcpListener(IPAddress, Int32) 新实例初始化 TcpListener 类用于侦听传入的连接尝试在指定的本地 IP 地址和端口号. TcpL ...
- 动态加载zTree,用key属性设置url链接、icon图标等
setting 举例 1. 设置 zTree 显示节点时,将 treeNode 的 xUrl 属性当做节点链接的目标 URL var setting = { data: { key: { url: ...
- 听着好像很牛的特效——幽灵按钮DOM
给大家分享一个听着好像很牛的东西——幽灵按钮,这个玩意对于艺术设计细胞在高中决定不在考试试卷上画画的我来说,实在不感冒.但是这个按钮的设计元素很流行,一个网页东西不做几个,光放上几个按钮就会显得很高端 ...