来自:http://f.dataguru.cn/thread-271645-1-1.html

简介

本文主要介绍下面4个方面

1.为什么要使用CombineFileInputFormat

2.CombineFileInputFormat实现原理

3.怎样使用CombineFileInputFormat

4.现存的问题

使用CombineFileInputFormat的目的

在开发MR的程序时,mapper的主要作用是对数据的收集。一般情况下,为了能让mapper更快的运行,我们会对文件进行split,以便多个mapper同时运行。在这种情况下,为了让程序更好更快的运行,我们需要控制mapper的个数。Mapper的个数主要由文件的大小及我们所设置的mapred.min.split.size以及blockSize所决定(详细参考:http://ai-longyu.iteye.com/blog/1566633)

上面所说的在我们使用TextInputFormat和分析单个文件时是没有问题的,基本上mapper的个数能够控制在我们所预期的范围内。但是当我们使用多个文件作为input的时候,mapper的个数就不再是我们所期望的那样了,因为TextInputFormat继承的是FileInputFormat,而FileInputFormat的split操作是只针对单个文件,对于多个文件,是将每个文件进行split,而不能做一些合并的操作(尤其是大量的小文件)。

你会想为什么不能进行合并呢,有没有实现合并的split呢?在这个时候,CombineFileInputFormat就闪亮登场了。这里所说的CombineFileInputFormat是由官方提供的,只要我们搞清楚了官方是怎么实现的,就能够自己也实现一个了。接下来将逐步分析CombineFileInputFormat的实现了。

CombineFileInputFormat实现步骤

这里插一句,官方的CombineFileInputFormat并不是线程安全的。

先申明一下,这里分析所采用的源码是apache的1.0.3,分析的在org.apache.hadoop.mapred.lib.CombineFileInputFormat而不是org.apache.hadoop.mapreduce.lib.input.CombineFileInputFormat,这里分析的旧API,而没有分析新的API

生成split的信息是由

  • public InputSplit[] getSplits(JobConf job, int numSplits)

Job参数:job的配置信息

numSplits参数:期望的mapper数目,在这里根本就没有使用

  • //每个DN的最小split大小
  • long minSizeNode = 0;
  • //同机架的最小split大小
  • long minSizeRack = 0;
  • //最大的split大小
  • long maxSize = 0;

这几个变量都可以从job的配置信息中获取

接下来就是获取input的路径列表,判断每个路径时候被Filter所允许,然后对允许的路径列表生成split信息列表,进入该类的核心方法

  • /**
  • * Return all the splits in the specified set of paths
  • *
  • * @param job Job的配置信息
  • * @param paths 输入源的路径列表
  • * @param maxSize 最大的split大小
  • * @param minSizeNode 每个DN最小的split大小
  • * @param minSizeRack 每个rack最小的split大小
  • * @param splits split信息列表
  • * @throws IOException
  • */
  • private void getMoreSplits(JobConf job, Path[] paths,
  • long maxSize, long minSizeNode, long minSizeRack,
  • List<CombineFileSplit> splits)

生成每个文件的OneFileInfo对象

  • // populate all the blocks for all files
  • long totLength = 0;
  • for (int i = 0; i < paths.length; i++) {
  • //构建每个input文件的信息,并将文件中的每个
  • //block信息收集到rackToBlocks、blockToNodes、nodeToBlocks中
  • files = new OneFileInfo(paths, job,  
  •                                  rackToBlocks, blockToNodes, nodeToBlocks);
  •       //增加所有文件的大小
  •       totLength += files.getLength();
  •     } 

在下面就开始真正的生成Split信息了

第一次:将同DN上的所有block生成Split,生成方式:

1.循环nodeToBlocks,获得每个DN上有哪些block

2.循环这些block列表

3.将block从blockToNodes中移除,避免同一个block被包含在多个split中

4.将该block添加到一个有效block的列表中,这个列表主要是保留哪些block已经从blockToNodes中被移除了,方便后面恢复到blockToNodes中

5.向临时变量curSplitSize增加block的大小

6.判断curSplitSize是否已经超过了设置的maxSize

a) 如果超过,执行并添加split信息,并重置curSplitSize和validBlocks

b) 没有超过,继续循环block列表,跳到第2步

7.当前DN上的block列表循环完成,判断剩余的block是否允许被split(剩下的block大小之和是否大于每个DN的最小split大小)

a) 如果允许,执行并添加split信息

b) 如果不被允许,将这些剩余的block归还blockToNodes

8.重置

9.跳到步骤1

  • // process all nodes and create splits that are local
  • // to a node.
  • //创建同一个DN上的split
  • for (Iterator<Map.Entry<String,
  • List<OneBlockInfo>>> iter = nodeToBlocks.entrySet().iterator();
  • iter.hasNext() {
  • Map.Entry<String, List<OneBlockInfo>> one = iter.next();
  • nodes.add(one.getKey());
  • List<OneBlockInfo> blocksInNode = one.getValue();
  • // for each block, copy it into validBlocks. Delete it from
  • // blockToNodes so that the same block does not appear in
  • // two different splits.
  • for (OneBlockInfo oneblock : blocksInNode) {
  • if (blockToNodes.containsKey(oneblock)) {
  • validBlocks.add(oneblock);
  • blockToNodes.remove(oneblock);
  • curSplitSize += oneblock.length;
  • // if the accumulated split size exceeds the maximum, then
  • // create this split.
  • if (maxSize != 0 && curSplitSize >= maxSize) {
  • // create an input split and add it to the splits array
  • //创建这些block合并后的split,并将其split添加到split列表中
  • addCreatedSplit(job, splits, nodes, validBlocks);
  • //重置
  • curSplitSize = 0;
  • validBlocks.clear();
  • }
  • }
  • }
  • // if there were any blocks left over and their combined size is
  • // larger than minSplitNode, then combine them into one split.
  • // Otherwise add them back to the unprocessed pool. It is likely
  • // that they will be combined with other blocks from the same rack later on.
  • //其实这里的注释已经说的很清楚,我再按照我的理解说一下
  • /**
  • * 这里有几种情况:
  • * 1、在这个DN上还有没有被split的block,
  • * 而且这些block的大小大于了在一个DN上的split最小值(没有达到最大值),
  • * 将把这些block合并成一个split
  • * 2、剩余的block的大小还是没有达到,将剩余的这些block
  • * 归还给blockToNodes,等以后统一处理
  • */
  • if (minSizeNode != 0 && curSplitSize >= minSizeNode) {
  • // create an input split and add it to the splits array
  • addCreatedSplit(job, splits, nodes, validBlocks);
  • } else {
  • for (OneBlockInfo oneblock : validBlocks) {
  • blockToNodes.put(oneblock, oneblock.hosts);
  • }
  • }
  • validBlocks.clear();
  • nodes.clear();
  • curSplitSize = 0;
  • }


第二次:对不再同一个DN上但是在同一个Rack上的block进行合并(只是之前还剩下的block)

  • // if blocks in a rack are below the specified minimum size, then keep them
  • // in 'overflow'. After the processing of all racks is complete, these overflow
  • // blocks will be combined into splits.
  • ArrayList<OneBlockInfo> overflowBlocks = new ArrayList<OneBlockInfo>();
  • ArrayList<String> racks = new ArrayList<String>();
  • // Process all racks over and over again until there is no more work to do.
  • //这里处理的就不再是同一个DN上的block
  • //同一个DN上的已经被处理过了(上面的代码),这里是一些
  • //还没有被处理的block
  • while (blockToNodes.size() > 0) {
  • // Create one split for this rack before moving over to the next rack.
  • // Come back to this rack after creating a single split for each of the
  • // remaining racks.
  • // Process one rack location at a time, Combine all possible blocks that
  • // reside on this rack as one split. (constrained by minimum and maximum
  • // split size).
  • // iterate over all racks
  • //创建同机架的split
  • for (Iterator<Map.Entry<String, List<OneBlockInfo>>> iter =
  • rackToBlocks.entrySet().iterator(); iter.hasNext() {
  • Map.Entry<String, List<OneBlockInfo>> one = iter.next();
  • racks.add(one.getKey());
  • List<OneBlockInfo> blocks = one.getValue();
  • // for each block, copy it into validBlocks. Delete it from
  • // blockToNodes so that the same block does not appear in
  • // two different splits.
  • boolean createdSplit = false;
  • for (OneBlockInfo oneblock : blocks) {
  • //这里很重要,现在的blockToNodes说明的是还有哪些block没有被split
  • if (blockToNodes.containsKey(oneblock)) {
  • validBlocks.add(oneblock);
  • blockToNodes.remove(oneblock);
  • curSplitSize += oneblock.length;
  • // if the accumulated split size exceeds the maximum, then
  • // create this split.
  • if (maxSize != 0 && curSplitSize >= maxSize) {
  • // create an input split and add it to the splits array
  • addCreatedSplit(job, splits, getHosts(racks), validBlocks);
  • createdSplit = true;
  • break;
  • }
  • }
  • }
  • // if we created a split, then just go to the next rack
  • if (createdSplit) {
  • curSplitSize = 0;
  • validBlocks.clear();
  • racks.clear();
  • continue;
  • }
  • //还有没有被split的block
  • //如果这些block的大小大于了同机架的最小split,
  • //则创建split
  • //否则,将这些block留到后面处理
  • if (!validBlocks.isEmpty()) {
  • if (minSizeRack != 0 && curSplitSize >= minSizeRack) {
  • // if there is a mimimum size specified, then create a single split
  • // otherwise, store these blocks into overflow data structure
  • addCreatedSplit(job, splits, getHosts(racks), validBlocks);
  • } else {
  • // There were a few blocks in this rack that remained to be processed.
  • // Keep them in 'overflow' block list. These will be combined later.
  • overflowBlocks.addAll(validBlocks);
  • }
  • }
  • curSplitSize = 0;
  • validBlocks.clear();
  • racks.clear();
  • }
  • }


最后,对于既不在同DN也不在同rack的block进行合并(经过前两步还剩下的block),这里源码就没有什么了,就不再贴了


源码总结:

合并,经过了3个步骤。同DN----》同rack不同DN-----》不同rack

将可以合并的block写到同一个split中

使用自定义的CombineFileInputFormat

MultiFileCombineInputFormat


  • package org.rollinkin.hadoop;
  • import java.io.IOException;
  • import org.apache.hadoop.io.LongWritable;
  • import org.apache.hadoop.io.Text;
  • import org.apache.hadoop.mapred.InputSplit;
  • import org.apache.hadoop.mapred.JobConf;
  • import org.apache.hadoop.mapred.RecordReader;
  • import org.apache.hadoop.mapred.Reporter;
  • import org.apache.hadoop.mapred.lib.CombineFileInputFormat;
  • import org.apache.hadoop.mapred.lib.CombineFileRecordReader;
  • import org.apache.hadoop.mapred.lib.CombineFileSplit;
  • /**
  • * 多文件合并split的输入format
  • *
  • * @author rollinkin
  • * @date 2012-10-29
  • * @version 1.0
  • * @since 1.0
  • */
  • public class MultiFileCombineInputFormat extends
  • CombineFileInputFormat<LongWritable, Text> {
  • @Override
  • public RecordReader<LongWritable, Text> getRecordReader(
  • InputSplit split, JobConf job, Reporter reporter)
  • throws IOException {
  • @SuppressWarnings({ "rawtypes", "unchecked" })
  • Class<RecordReader<LongWritable, Text>> rrClass = (Class)CombineLineRecordReader.class;
  • return new CombineFileRecordReader<LongWritable, Text>(job,(CombineFileSplit) split, reporter,rrClass);
  • }
  • }

CombineLineRecordReader,这个其实没有什么内容,就是包装了一个Reader


  • package org.rollinkin.hadoop;
  • import java.io.IOException;
  • import org.apache.hadoop.conf.Configuration;
  • import org.apache.hadoop.io.LongWritable;
  • import org.apache.hadoop.io.Text;
  • import org.apache.hadoop.mapred.FileSplit;
  • import org.apache.hadoop.mapred.LineRecordReader;
  • import org.apache.hadoop.mapred.RecordReader;
  • import org.apache.hadoop.mapred.Reporter;
  • import org.apache.hadoop.mapred.lib.CombineFileSplit;
  • public class CombineLineRecordReader implements
  • RecordReader<LongWritable, Text> {
  • private LineRecordReader delegate;
  • public CombineLineRecordReader(CombineFileSplit split, Configuration conf,
  • Reporter reporter, Integer idx) throws IOException {
  • FileSplit fileSplit = new FileSplit(split.getPath(idx),
  • split.getOffset(idx), split.getLength(idx),
  • split.getLocations());
  • delegate = new LineRecordReader(conf, fileSplit);
  • }
  • @Override
  • public boolean next(LongWritable key, Text value) throws IOException {
  • return delegate.next(key, value);
  • }
  • @Override
  • public LongWritable createKey() {
  • return delegate.createKey();
  • }
  • @Override
  • public Text createValue() {
  • return delegate.createValue();
  • }
  • @Override
  • public long getPos() throws IOException {
  • return delegate.getPos();
  • }
  • @Override
  • public void close() throws IOException {
  • delegate.close();
  • }
  • @Override
  • public float getProgress() throws IOException {
  • return delegate.getProgress();
  • }
  • }


具体的使用我就不再留了,其实很简单,就是把你的InputFormat设置成MultiFileCombineInputFormat 就可以了(在2012-11-09之前提供了一个reader实际上是不可用,他存在跨块读取的问题,

这里就不在提供了。如果使用了,请更新一下。哎,又传播错误的消息了)

现存问题

    • 合并后会造成mapper不能本地化,带来mapper的额外开销,需要权衡
    • 这里只实现了简单的Text的方式的合并,对于可压缩的、二进制等文件没有提供
    • 这里提供的自定义的实现,只是简单的按行读取

hadoop old API CombineFileInputFormat的更多相关文章

  1. hadoop的API对HDFS上的文件访问

    这篇文章主要介绍了使用hadoop的API对HDFS上的文件访问,其中包括上传文件到HDFS上.从HDFS上下载文件和删除HDFS上的文件,需要的朋友可以参考下hdfs文件操作操作示例,包括上传文件到 ...

  2. Python3调用Hadoop的API

    前言: 上一篇文章 我学习使用pandas进行简单的数据分析,但是各位...... Pandas处理.分析不了TB级别数据的大数据,于是再看看Hadoop. 另附上人心不足蛇吞象 对故事一的感悟:   ...

  3. 通过流的方式操作hadoop的API

    通过流的方式操作hadoop的API 功能: 可以直接用来操作hadoop的文件系统 可以用在mapreduce的outputformat中设置RecordWrite 参考: 概念理解 http:// ...

  4. hadoop: hdfs API示例

    利用hdfs的api,可以实现向hdfs的文件.目录读写,利用这一套API可以设计一个简易的山寨版云盘,见下图: 为了方便操作,将常用的文件读写操作封装了一个工具类: import org.apach ...

  5. Hadoop Java API 操作 hdfs--1

    Hadoop文件系统是一个抽象的概念,hdfs仅仅是Hadoop文件系统的其中之一. 就hdfs而言,访问该文件系统有两种方式:(1)利用hdfs自带的命令行方式,此方法类似linux下面的shell ...

  6. HADOOP的API简单介绍

    public class HdfsClient { FileSystem fs = null; @Before public void init() throws Exception { // 构造一 ...

  7. 在本地调用hadoop的api

    第一次在本地运行Java代码,调用hadoop的hdfs的api接口,遇到下面的问题: 1.HADOOP_HOME and hadoop.home.dir are unset 解决办法:在本地安装配置 ...

  8. hadoop 文件系统API操作

    配置参数:-DHADOOP_USER_NAME=hadoop public class HdfsUtils { private static FileSystem fileSystem; @Befor ...

  9. Linux 下 Hadoop java api 问题

    1. org.apache.hadoop.security.AccessControlException: Permission denied: user=opsuser, access=WRITE, ...

随机推荐

  1. python测试开发django-14.查询表结果(超详细)

    前言 django查询数据库的方法很多,不同的方法返回的结果也不太一样,本篇详细讲解关于查询的13个方法 返回对象是对象列表的: all(), filter(), exclude(), order_b ...

  2. 基于Vue、Bootstrap的Tab形式的进度展示

    最近基于Vue.Bootstrap做了一个箭头样式的进度展示的单页应用,并且支持了对于一个本地JS文件的检索,通过这个单页应用,对于Vue的理解又深入了一些.在这里把主要的代码分享出来. 本单页应用实 ...

  3. 为何要对URL进行编码

    为何要对URL进行编码 我们都知道Http协议中参数的传输是"key=value"这种简直对形式的,如果要传多个参数就需要用“&”符号对键值对进行分割.如"?na ...

  4. CSS3 Flex布局整理(二)-容器属性

    一.Flex容器属性介绍 1.flex-flow :水平或垂直方向上的流动方式,包裹处理,其中包括了flex-direction属性和flex-wrap属性. 2.justify-content:定义 ...

  5. [转]PHP资源列表

    转自:http://www.cnblogs.com/CraryPrimitiveMan/p/4437272.html 一个PHP资源列表,内容包括:库.框架.模板.安全.代码分析.日志.第三方库.配置 ...

  6. [转]redis配置文件redis.conf的详细说明

    转自: http://www.sufeinet.com/thread-8047-1-1.html # Redis 配置文件 # 当配置中需要配置内存大小时,可以使用 1k, 5GB, 4M 等类似的格 ...

  7. CF 329B(Biridian Forest-贪心-非二分)

    B. Biridian Forest time limit per test 2 seconds memory limit per test 256 megabytes input standard ...

  8. Java系列:使用软引用构建敏感数据的缓存

    一.为什么需要使用软引用    首先,我们看一个雇员信息查询系统的实例.我们将使用一个Java语言实现的雇员信息查询系统查询存储在磁盘文件或者数据库中的雇员人事档案信息.作为一个用户,我们完全有可能需 ...

  9. 一个可用来记录Isilon各个节点的CPU,网络,磁盘性能的命令

    通过查看命令isi statistics system的帮助信息,拼出了下面的命令. isi statistics system list --nodes=all --degraded --forma ...

  10. 查看LINQ Expression編譯後的SQL語法(转)

    在用了LINQ語法之後的一個月,我幾乎把SQL語法全部拋到腦後了,不過 LINQ好用歸好用,但是實際上操作資料庫的還是SQL語法,如果不知道LINQ語法 編譯過後產生怎樣的SQL語法,一不小心效能就會 ...