转自:http://blog.csdn.net/androidlushangderen/article/details/41114259

昨天经过几个小时的学习,把MapReduce的第一个阶段的过程学习了一下,也就是最最开始的时候从文件中的Data到key-value的映射,也就是InputFormat的过程。虽说过程不是很难,但是也存在很多细节的。也很少会有人对此做比较细腻的研究,学习。今天,就让我来为大家剖析一下这段代码的原理。我还为此花了一点时间做了几张结构图,便于大家理解。在这里先声明一下,我研究的MapReduce主要研究的是旧版的API,也就是mapred包下的。

InputFormat最最原始的形式就是一个接口。后面出现的各种Format都是他的衍生类。结构如下,只包含最重要的2个方法:

  1. public interface InputFormat<K, V> {
  2. /**
  3. * Logically split the set of input files for the job.
  4. *
  5. * <p>Each {@link InputSplit} is then assigned to an individual {@link Mapper}
  6. * for processing.</p>
  7. *
  8. * <p><i>Note</i>: The split is a <i>logical</i> split of the inputs and the
  9. * input files are not physically split into chunks. For e.g. a split could
  10. * be <i><input-file-path, start, offset></i> tuple.
  11. *
  12. * @param job job configuration.
  13. * @param numSplits the desired number of splits, a hint.
  14. * @return an array of {@link InputSplit}s for the job.
  15. */
  16. InputSplit[] getSplits(JobConf job, int numSplits) throws IOException;
  17. /**
  18. * Get the {@link RecordReader} for the given {@link InputSplit}.
  19. *
  20. * <p>It is the responsibility of the <code>RecordReader</code> to respect
  21. * record boundaries while processing the logical split to present a
  22. * record-oriented view to the individual task.</p>
  23. *
  24. * @param split the {@link InputSplit}
  25. * @param job the job that this split belongs to
  26. * @return a {@link RecordReader}
  27. */
  28. RecordReader<K, V> getRecordReader(InputSplit split,
  29. JobConf job,
  30. Reporter reporter) throws IOException;
  31. }

所以后面讲解,我也只是会围绕这2个方法进行分析。当然我们用的最多的是从文件中获得输入数据,也就是FileInputFormat这个类。继承关系如下:

  1. public abstract class FileInputFormat<K, V> implements InputFormat<K, V>

我们看里面的1个主要方法:

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

返回的类型是一个InputSpilt对象,这是一个抽象的输入Spilt分片概念。结构如下:

  1. public interface InputSplit extends Writable {
  2. /**
  3. * Get the total number of bytes in the data of the <code>InputSplit</code>.
  4. *
  5. * @return the number of bytes in the input split.
  6. * @throws IOException
  7. */
  8. long getLength() throws IOException;
  9. /**
  10. * Get the list of hostnames where the input split is located.
  11. *
  12. * @return list of hostnames where data of the <code>InputSplit</code> is
  13. *         located as an array of <code>String</code>s.
  14. * @throws IOException
  15. */
  16. String[] getLocations() throws IOException;
  17. }

提供了与数据相关的2个方法。后面这个返回的值会被用来传递给RecordReader里面去的。在想理解getSplits方法之前还有一个类需要理解,FileStatus,里面包装了一系列的文件基本信息方法:

  1. public class FileStatus implements Writable, Comparable {
  2. private Path path;
  3. private long length;
  4. private boolean isdir;
  5. private short block_replication;
  6. private long blocksize;
  7. private long modification_time;
  8. private long access_time;
  9. private FsPermission permission;
  10. private String owner;
  11. private String group;

.....

看到这里你估计会有点晕了,下面是我做的一张小小类图关系:

可以看到,FileSpilt为了兼容新老版本,继承了新的抽象类InputSpilt,同时附上旧的接口形式的InputSpilt。下面我们看看里面的getspilt核心过程:

  1. /** Splits files returned by {@link #listStatus(JobConf)} when
  2. * they're too big.*/
  3. @SuppressWarnings("deprecation")
  4. public InputSplit[] getSplits(JobConf job, int numSplits)
  5. throws IOException {
  6. //获取所有的状态文件
  7. FileStatus[] files = listStatus(job);
  8. // Save the number of input files in the job-conf
  9. //在job-cof中保存文件的数量
  10. job.setLong(NUM_INPUT_FILES, files.length);
  11. long totalSize = 0;
  12. // compute total size,计算文件总的大小
  13. for (FileStatus file: files) {                // check we have valid files
  14. if (file.isDir()) {
  15. //如果是目录不是纯文件的直接抛异常
  16. throw new IOException("Not a file: "+ file.getPath());
  17. }
  18. totalSize += file.getLen();
  19. }
  20. //用户期待的划分大小,总大小除以spilt划分数目
  21. long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
  22. //获取系统的划分最小值
  23. long minSize = Math.max(job.getLong("mapred.min.split.size", 1),
  24. minSplitSize);
  25. // generate splits
  26. //创建numSplits个FileSpilt文件划分量
  27. ArrayList<FileSplit> splits = new ArrayList<FileSplit>(numSplits);
  28. NetworkTopology clusterMap = new NetworkTopology();
  29. for (FileStatus file: files) {
  30. Path path = file.getPath();
  31. FileSystem fs = path.getFileSystem(job);
  32. long length = file.getLen();
  33. //获取此文件的block的位置列表
  34. BlockLocation[] blkLocations = fs.getFileBlockLocations(file, 0, length);
  35. //如果文件系统可划分
  36. if ((length != 0) && isSplitable(fs, path)) {
  37. //计算此文件的总的block块的大小
  38. long blockSize = file.getBlockSize();
  39. //根据期待大小,最小大小,得出最终的split分片大小
  40. long splitSize = computeSplitSize(goalSize, minSize, blockSize);
  41. long bytesRemaining = length;
  42. //如果剩余待划分字节倍数为划分大小超过1.1的划分比例,则进行拆分
  43. while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
  44. //获取提供数据的splitHost位置
  45. String[] splitHosts = getSplitHosts(blkLocations,
  46. length-bytesRemaining, splitSize, clusterMap);
  47. //添加FileSplit
  48. splits.add(new FileSplit(path, length-bytesRemaining, splitSize,
  49. splitHosts));
  50. //数量减少splitSize大小
  51. bytesRemaining -= splitSize;
  52. }
  53. if (bytesRemaining != 0) {
  54. //添加刚刚剩下的没划分完的部分,此时bytesRemaining已经小于splitSize的1.1倍了
  55. splits.add(new FileSplit(path, length-bytesRemaining, bytesRemaining,
  56. blkLocations[blkLocations.length-1].getHosts()));
  57. }
  58. } else if (length != 0) {
  59. //不划分,直接添加Spilt
  60. String[] splitHosts = getSplitHosts(blkLocations,0,length,clusterMap);
  61. splits.add(new FileSplit(path, 0, length, splitHosts));
  62. } else {
  63. //Create empty hosts array for zero length files
  64. splits.add(new FileSplit(path, 0, length, new String[0]));
  65. }
  66. }
  67. //最后返回FileSplit数组
  68. LOG.debug("Total # of splits: " + splits.size());
  69. return splits.toArray(new FileSplit[splits.size()]);
  70. }

里面有个computerSpiltSize方法很特殊,考虑了很多情况,总之最小值不能小于系统设定的最小值。要与期待值,块大小,系统允许最小值:

  1. protected long computeSplitSize(long goalSize, long minSize,
  2. long blockSize) {
  3. return Math.max(minSize, Math.min(goalSize, blockSize));
  4. }

上述过程的相应流程图如下:

3种情况3中年执行流程。

处理完getSpilt方法然后,也就是说已经把数据从文件中转划到InputSpilt中了,接下来就是给RecordRead去取出里面的一条条的记录了。当然这在FileInputFormat是抽象方法,必须由子类实现的,我在这里挑出了2个典型的子类SequenceFileInputFormat,和TextInputFormat。他们的实现RecordRead方法如下:

  1. public RecordReader<K, V> getRecordReader(InputSplit split,
  2. JobConf job, Reporter reporter)
  3. throws IOException {
  4. reporter.setStatus(split.toString());
  5. return new SequenceFileRecordReader<K, V>(job, (FileSplit) split);
  6. }
  1. public RecordReader<LongWritable, Text> getRecordReader(
  2. InputSplit genericSplit, JobConf job,
  3. Reporter reporter)
  4. throws IOException {
  5. reporter.setStatus(genericSplit.toString());
  6. return new LineRecordReader(job, (FileSplit) genericSplit);
  7. }

可以看到里面的区别就在于LineRecordReader和SequenceFileRecordReader的不同了,这也就表明2种方式对应于数据的读取方式可能会不一样,继续往里深入看:

  1. /** An {@link RecordReader} for {@link SequenceFile}s. */
  2. public class SequenceFileRecordReader<K, V> implements RecordReader<K, V> {
  3. private SequenceFile.Reader in;
  4. private long start;
  5. private long end;
  6. private boolean more = true;
  7. protected Configuration conf;
  8. public SequenceFileRecordReader(Configuration conf, FileSplit split)
  9. throws IOException {
  10. Path path = split.getPath();
  11. FileSystem fs = path.getFileSystem(conf);
  12. //从文件系统中读取数据输入流
  13. this.in = new SequenceFile.Reader(fs, path, conf);
  14. this.end = split.getStart() + split.getLength();
  15. this.conf = conf;
  16. if (split.getStart() > in.getPosition())
  17. in.sync(split.getStart());                  // sync to start
  18. this.start = in.getPosition();
  19. more = start < end;
  20. }
  21. ......
  22. /**
  23. * 获取下一个键值对
  24. */
  25. public synchronized boolean next(K key, V value) throws IOException {
  26. //判断还有无下一条记录
  27. if (!more) return false;
  28. long pos = in.getPosition();
  29. boolean remaining = (in.next(key) != null);
  30. if (remaining) {
  31. getCurrentValue(value);
  32. }
  33. if (pos >= end && in.syncSeen()) {
  34. more = false;
  35. } else {
  36. more = remaining;
  37. }
  38. return more;
  39. }

我们可以看到SequenceFileRecordReader是从输入流in中一个键值,一个键值的读取,另外一个的实现方式如下:

  1. /**
  2. * Treats keys as offset in file and value as line.
  3. */
  4. public class LineRecordReader implements RecordReader<LongWritable, Text> {
  5. private static final Log LOG
  6. = LogFactory.getLog(LineRecordReader.class.getName());
  7. private CompressionCodecFactory compressionCodecs = null;
  8. private long start;
  9. private long pos;
  10. private long end;
  11. private LineReader in;
  12. int maxLineLength;
  13. ....
  14. /** Read a line. */
  15. public synchronized boolean next(LongWritable key, Text value)
  16. throws IOException {
  17. while (pos < end) {
  18. //设置key
  19. key.set(pos);
  20. //根据位置一行一行读取,设置value
  21. int newSize = in.readLine(value, maxLineLength,
  22. Math.max((int)Math.min(Integer.MAX_VALUE, end-pos),
  23. maxLineLength));
  24. if (newSize == 0) {
  25. return false;
  26. }
  27. pos += newSize;
  28. if (newSize < maxLineLength) {
  29. return true;
  30. }
  31. // line too long. try again
  32. LOG.info("Skipped line of size " + newSize + " at pos " + (pos - newSize));
  33. }
  34. return false;
  35. }

实现的方式为通过读的位置,从输入流中逐行读取key-value。通过这2种方法,就能得到新的key-value,就会用于后面的map操作。

InputFormat的整个流程其实我忽略了很多细节。大体流程如上述所说。

MapReduce的InputFormat过程的学习的更多相关文章

  1. 第2节 mapreduce深入学习:7、MapReduce的规约过程combiner

    第2节 mapreduce深入学习:7.MapReduce的规约过程combiner 每一个 map 都可能会产生大量的本地输出,Combiner 的作用就是对 map 端的输出先做一次合并,以减少在 ...

  2. gdb调试汇编堆栈过程的学习

    gdb调试汇编堆栈过程的学习 以下为C源文件 使用gcc - g code.c -o code -m32指令在64位的机器上产生32位汇编,然后使用gdb example指令进入gdb调试器: 进入之 ...

  3. MapReduce的Shuffle过程介绍

    MapReduce的Shuffle过程介绍 Shuffle的本义是洗牌.混洗,把一组有一定规则的数据尽量转换成一组无规则的数据,越随机越好.MapReduce中的Shuffle更像是洗牌的逆过程,把一 ...

  4. 【Hadoop离线基础总结】MapReduce自定义InputFormat和OutputFormat案例

    MapReduce自定义InputFormat和OutputFormat案例 自定义InputFormat 合并小文件 需求 无论hdfs还是mapreduce,存放小文件会占用元数据信息,白白浪费内 ...

  5. MapReduce的InputFormat学习过程

    昨天,经过几个小时的学习.该MapReduce学习的某一位的方法的第一阶段.即当大多数文件的开头的Data至key-value制图.那是,InputFormat的过程.虽说过程不是非常难,可是也存在非 ...

  6. Hadoop MapReduce的Shuffle过程

    一.概述 理解Hadoop的Shuffle过程是一个大数据工程师必须的,笔者自己将学习笔记记录下来,以便以后方便复习查看. 二. MapReduce确保每个reducer的输入都是按键排序的.系统执行 ...

  7. MapReduce框架原理-InputFormat数据输入

    InputFormat简介 InputFormat:管控MR程序文件输入到Mapper阶段,主要做两项操作:怎么去切片?怎么将切片数据转换成键值对数据. InputFormat是一个抽象类,没有实现怎 ...

  8. DHCP协议格式、DHCP服务搭建、DHCP协商交互过程入门学习

    相关学习资料 http://www.rfc-editor.org/rfc/rfc2131.txt http://baike.baidu.com/view/7992.htm?fromtitle=DHCP ...

  9. [工具向]__关于androidstudio工具使用过程中学习到的一些知识点简记

    前言 在我学习android开发课程的过程中,我们通常只会关注编程语言上面的一些知识点与问题,而忽略了开发工具的使用上的一些遇到的一些知识,其实每一款IDE工具都是集编程语言大成而开发出来的,其中有很 ...

随机推荐

  1. 网络电台-SHOUTcast

    网络电台种类 目前的网络电台网站一般是基于以下三种协议的: mms.rtsp.http 其中mms是微软公司提出的网络流媒体协议,通常采用wma格式的文件,Android现在还不支持这种协议,也不支持 ...

  2. ASP.NET Core Kestrel 随机404错误

    一.Bug 出现 最近遇到一个很诡异的bug,Visual Studio 2017调试ASP.NET Core 2.2 Web程序的时候,随机性的出现404错误.如下图 事实上这个css文件是存在的, ...

  3. 格式化文本数据抽取工具awk

    在管理和维护Linux系统过程中,有时可能需要从一个具有一定格式的文本(格式化文本)中抽取数据,这时可以使用awk编辑器来完成这项任务.发明这个工具的作者是Aho.Weinberg和Kernighan ...

  4. Oracle 检索数据

    SELECT  *  |    {   [ DISTINCT  ]    column   |    expression   [   alias   ]  ,   ...    } FROM  ta ...

  5. TP框架中如何使用SESSION限制登录?

    TP框架中如何使用SESSION限制登录? 之前总是被问题今天才明白,最高效的来做页面访问限制问题. OOP思想中的继承特性,实现验证,是否已经登录,不必每个页面都进行判断. 实现如下: 继承Cont ...

  6. 【微信开发】JS和PHP分别判断当前浏览器是否微信浏览器

    1.PHP端 //判断是否微信浏览器 -xzz1125 function is_weixin() { if (strpos($_SERVER['HTTP_USER_AGENT'], 'MicroMes ...

  7. LeetCode-2: Add Two Numbers

    [Problem:2-Add Two Numbers] You are given two non-empty linked lists representing two non-negative i ...

  8. laravel的cookie操作

    前提你的整个http流程一定要走完,页就是必须走到view()或Response,你写到中间中断调试,cookie是设置不上去的......(坑~~) 读取: $value = Cookie::get ...

  9. spine 所有动画的第一帧必须把所有能K的都K上

    spine 所有动画的第一帧必须把所有能K的都K上.否则在快速切换动画时会出问题.

  10. Informix 語法

    1.修改表名稱 RENAME TABLE old_table_name TO new_table_name; 2.分頁 select  SKIP 0 FIRST 1 * from tablename ...