花了大约1周的时间,最终把MapReduce的5大阶段的源代码学习结束掉了。收获不少。就算本人对Hadoop学习的一个里程碑式的纪念吧。今天花了一点点的时间,把MapReduce的最后一个阶段。输出OutputFormat给做了分析,这个过程跟InputFormat刚刚好是对着干的,二者极具对称性。为什么这么说呢,待我一一分析。

OutputFormat过程的作用就是定义数据key-value的输出格式,给你处理好后的数据,到底以什么样的形式输出呢。才干让下次别人拿到这个文件的时候能准确的提取出里面的数据。这里,我们撇开这个话题,只我知道的一些定义的数据格式的方法,比方在Redis中会有这种设计:

[key-length][key][value-length][value][key-length][key][value-length][value]...

或者说不一定非要省空间,直接搞过分隔符

[key]   [value]\n

[key]   [value]\n

[key]   [value]\n

.....

这样逐行读取,再以空格隔开,取出里面的键值对。这么做简单是简单。就是不紧凑,空间浪费得有点多。在MapReduce的OutputFormat的有种格式用的就是这样的方式。

首先必须得了解OutputFormat里面究竟有什么东西:

  1. public interface OutputFormat<K, V> {
  2.  
  3. /**
  4. * Get the {@link RecordWriter} for the given job.
  5. * 获取输出记录键值记录
  6. *
  7. * @param ignored
  8. * @param job configuration for the job whose output is being written.
  9. * @param name the unique name for this part of the output.
  10. * @param progress mechanism for reporting progress while writing to file.
  11. * @return a {@link RecordWriter} to write the output for the job.
  12. * @throws IOException
  13. */
  14. RecordWriter<K, V> getRecordWriter(FileSystem ignored, JobConf job,
  15. String name, Progressable progress)
  16. throws IOException;
  17.  
  18. /**
  19. * Check for validity of the output-specification for the job.
  20. *
  21. * <p>This is to validate the output specification for the job when it is
  22. * a job is submitted. Typically checks that it does not already exist,
  23. * throwing an exception when it already exists, so that output is not
  24. * overwritten.</p>
  25. * 作业执行之前进行的检測工作,比如配置的输出文件夹是否存在等
  26. *
  27. * @param ignored
  28. * @param job job configuration.
  29. * @throws IOException when output should not be attempted
  30. */
  31. void checkOutputSpecs(FileSystem ignored, JobConf job) throws IOException;
  32. }

非常easy的2个方法,RecordWriter比較重要,后面的key-value的写入操作都是依据他来完毕的。可是他是一个接口,在MapReduce中,我们用的最多的他的子类是FileOutputFormat:

  1. /** A base class for {@link OutputFormat}. */
  2. public abstract class FileOutputFormat<K, V> implements OutputFormat<K, V> {

他是一个抽象类。可是实现了接口中的第二个方法checkOutputSpecs()方法:

  1. public void checkOutputSpecs(FileSystem ignored, JobConf job)
  2. throws FileAlreadyExistsException,
  3. InvalidJobConfException, IOException {
  4. // Ensure that the output directory is set and not already there
  5. Path outDir = getOutputPath(job);
  6. if (outDir == null && job.getNumReduceTasks() != 0) {
  7. throw new InvalidJobConfException("Output directory not set in JobConf.");
  8. }
  9. if (outDir != null) {
  10. FileSystem fs = outDir.getFileSystem(job);
  11. // normalize the output directory
  12. outDir = fs.makeQualified(outDir);
  13. setOutputPath(job, outDir);
  14.  
  15. // get delegation token for the outDir's file system
  16. TokenCache.obtainTokensForNamenodes(job.getCredentials(),
  17. new Path[] {outDir}, job);
  18.  
  19. // check its existence
  20. if (fs.exists(outDir)) {
  21. //假设输出文件夹以及存在,则抛异常
  22. throw new FileAlreadyExistsException("Output directory " + outDir +
  23. " already exists");
  24. }
  25. }
  26. }

就是检查输出文件夹在不在的操作。在这个类里还出现了一个辅助类:

  1. public static Path getTaskOutputPath(JobConf conf, String name)
  2. throws IOException {
  3. // ${mapred.out.dir}
  4. Path outputPath = getOutputPath(conf);
  5. if (outputPath == null) {
  6. throw new IOException("Undefined job output-path");
  7. }
  8.  
  9. //依据OutputCommitter获取输出路径
  10. OutputCommitter committer = conf.getOutputCommitter();
  11. Path workPath = outputPath;
  12. TaskAttemptContext context = new TaskAttemptContext(conf,
  13. TaskAttemptID.forName(conf.get("mapred.task.id")));
  14. if (committer instanceof FileOutputCommitter) {
  15. workPath = ((FileOutputCommitter)committer).getWorkPath(context,
  16. outputPath);
  17. }
  18.  
  19. // ${mapred.out.dir}/_temporary/_${taskid}/${name}
  20. return new Path(workPath, name);
  21. }

就是上面OutputCommiter,里面定义了非常多和Task,job作业相关的方法。非常多时候都会与OutputFormat合作的形式出现。

他也有自己的子类实现FileOutputCommiter:

  1. public class FileOutputCommitter extends OutputCommitter {
  2.  
  3. public static final Log LOG = LogFactory.getLog(
  4. "org.apache.hadoop.mapred.FileOutputCommitter");
  5. /**
  6. * Temporary directory name
  7. */
  8. public static final String TEMP_DIR_NAME = "_temporary";
  9. public static final String SUCCEEDED_FILE_NAME = "_SUCCESS";
  10. static final String SUCCESSFUL_JOB_OUTPUT_DIR_MARKER =
  11. "mapreduce.fileoutputcommitter.marksuccessfuljobs";
  12.  
  13. public void setupJob(JobContext context) throws IOException {
  14. JobConf conf = context.getJobConf();
  15. Path outputPath = FileOutputFormat.getOutputPath(conf);
  16. if (outputPath != null) {
  17. Path tmpDir = new Path(outputPath, FileOutputCommitter.TEMP_DIR_NAME);
  18. FileSystem fileSys = tmpDir.getFileSystem(conf);
  19. if (!fileSys.mkdirs(tmpDir)) {
  20. LOG.error("Mkdirs failed to create " + tmpDir.toString());
  21. }
  22. }
  23. }
  24. ....

在Reduce阶段的后面的写阶段,FileOutputFormat是默认的输出的类型:

  1. //获取输出的key,value
  2. final RecordWriter<OUTKEY, OUTVALUE> out = new OldTrackingRecordWriter<OUTKEY, OUTVALUE>(
  3. reduceOutputCounter, job, reporter, finalName);
  4.  
  5. OutputCollector<OUTKEY,OUTVALUE> collector =
  6. new OutputCollector<OUTKEY,OUTVALUE>() {
  7. public void collect(OUTKEY key, OUTVALUE value)
  8. throws IOException {
  9. //将处理后的key,value写入输出流中。最后写入HDFS作为终于结果
  10. out.write(key, value);
  11. // indicate that progress update needs to be sent
  12. reporter.progress();
  13. }
  14. };

out就是直接发挥作用的类,可是是哪个Formtat的返回的呢。我们进入OldTrackingRecordWriter继续看:

  1. public OldTrackingRecordWriter(
  2. org.apache.hadoop.mapred.Counters.Counter outputRecordCounter,
  3. JobConf job, TaskReporter reporter, String finalName)
  4. throws IOException {
  5. this.outputRecordCounter = outputRecordCounter;
  6. //默认是FileOutputFormat文件输出方式
  7. this.fileOutputByteCounter = reporter
  8. .getCounter(FileOutputFormat.Counter.BYTES_WRITTEN);
  9. Statistics matchedStats = null;
  10. if (job.getOutputFormat() instanceof FileOutputFormat) {
  11. matchedStats = getFsStatistics(FileOutputFormat.getOutputPath(job), job);
  12. }
  13. fsStats = matchedStats;
  14.  
  15. FileSystem fs = FileSystem.get(job);
  16. long bytesOutPrev = getOutputBytes(fsStats);
  17. //从配置中获取作业的输出方式
  18. this.real = job.getOutputFormat().getRecordWriter(fs, job, finalName,
  19. reporter);
  20. long bytesOutCurr = getOutputBytes(fsStats);
  21. fileOutputByteCounter.increment(bytesOutCurr - bytesOutPrev);
  22. }

果然是我们所想的那样。FileOutputFormat,可是不要忘了它的getRecordWriter()是抽象方法,调用它还必须由它的子类来实现:

  1. public abstract RecordWriter<K, V> getRecordWriter(FileSystem ignored,
  2. JobConf job, String name,
  3. Progressable progress)
  4. throws IOException;

在这里我们举出当中在InputFormat举过的样例,TextOutputFormat,SequenceFileOutputFormat,与TextInputFormat,SequenceFileInputFormat相应。也就说说上面2个子类定义了2种截然不同的输出格式,也就返回了不一样的RecordWriter实现类.在TextOutputFormat中。他定义了一个叫LineRecordWriter的定义:

  1. public RecordWriter<K, V> getRecordWriter(FileSystem ignored,
  2. JobConf job,
  3. String name,
  4. Progressable progress)
  5. throws IOException {
  6. //从配置推断输出是否要压缩
  7. boolean isCompressed = getCompressOutput(job);
  8. //配置中获取加在key-value的分隔符
  9. String keyValueSeparator = job.get("mapred.textoutputformat.separator",
  10. "\t");
  11. //依据是否压缩获取对应的LineRecordWriter
  12. if (!isCompressed) {
  13. Path file = FileOutputFormat.getTaskOutputPath(job, name);
  14. FileSystem fs = file.getFileSystem(job);
  15. FSDataOutputStream fileOut = fs.create(file, progress);
  16. return new LineRecordWriter<K, V>(fileOut, keyValueSeparator);
  17. } else {
  18. Class<? extends CompressionCodec> codecClass =
  19. getOutputCompressorClass(job, GzipCodec.class);
  20. // create the named codec
  21. CompressionCodec codec = ReflectionUtils.newInstance(codecClass, job);
  22. // build the filename including the extension
  23. Path file =
  24. FileOutputFormat.getTaskOutputPath(job,
  25. name + codec.getDefaultExtension());
  26. FileSystem fs = file.getFileSystem(job);
  27. FSDataOutputStream fileOut = fs.create(file, progress);
  28. return new LineRecordWriter<K, V>(new DataOutputStream
  29. (codec.createOutputStream(fileOut)),
  30. keyValueSeparator);
  31. }

他以一个内部类的形式存在于TextOutputFormat。而在SequenceFileOutputFormat中,他的形式是如何的呢:

  1. public RecordWriter<K, V> getRecordWriter(
  2. FileSystem ignored, JobConf job,
  3. String name, Progressable progress)
  4. throws IOException {
  5. // get the path of the temporary output file
  6. Path file = FileOutputFormat.getTaskOutputPath(job, name);
  7.  
  8. FileSystem fs = file.getFileSystem(job);
  9. CompressionCodec codec = null;
  10. CompressionType compressionType = CompressionType.NONE;
  11. if (getCompressOutput(job)) {
  12. // find the kind of compression to do
  13. compressionType = getOutputCompressionType(job);
  14.  
  15. // find the right codec
  16. Class<? extends CompressionCodec> codecClass = getOutputCompressorClass(job,
  17. DefaultCodec.class);
  18. codec = ReflectionUtils.newInstance(codecClass, job);
  19. }
  20. final SequenceFile.Writer out =
  21. SequenceFile.createWriter(fs, job, file,
  22. job.getOutputKeyClass(),
  23. job.getOutputValueClass(),
  24. compressionType,
  25. codec,
  26. progress);
  27.  
  28. return new RecordWriter<K, V>() {
  29.  
  30. public void write(K key, V value)
  31. throws IOException {
  32.  
  33. out.append(key, value);
  34. }
  35.  
  36. public void close(Reporter reporter) throws IOException { out.close();}
  37. };
  38. }

关键的操作都在于SequenceFile.Writer中。有不同的RecordWriter就会有不同的写入数据的方式。这里我们举LineRecordWriter的样例。看看他的写入方法:

  1. //往输出流中写入key-value
  2. public synchronized void write(K key, V value)
  3. throws IOException {
  4.  
  5. //推断键值对是否为空
  6. boolean nullKey = key == null || key instanceof NullWritable;
  7. boolean nullValue = value == null || value instanceof NullWritable;
  8.  
  9. //假设k-v都为空,则操作失败,不写入直接返回
  10. if (nullKey && nullValue) {
  11. return;
  12. }
  13.  
  14. //假设key不空。则写入key
  15. if (!nullKey) {
  16. writeObject(key);
  17. }
  18.  
  19. //假设key,value都不为空,则中间写入k-v分隔符。在这里为\t空格符
  20. if (!(nullKey || nullValue)) {
  21. out.write(keyValueSeparator);
  22. }
  23.  
  24. //最后写入value
  25. if (!nullValue) {
  26. writeObject(value);
  27. }

在这种方法里。我们就能看出他的存储形式就是我刚刚在上面讲的另外一种存储方式。这种方法将会在以下的代码中被运行:

  1. OutputCollector<OUTKEY,OUTVALUE> collector =
  2. new OutputCollector<OUTKEY,OUTVALUE>() {
  3. public void collect(OUTKEY key, OUTVALUE value)
  4. throws IOException {
  5. //将处理后的key,value写入输出流中,最后写入HDFS作为终于结果
  6. out.write(key, value);
  7. // indicate that progress update needs to be sent
  8. reporter.progress();
  9. }
  10. };

过程能够这么理解:

collector.collect()------->out.write(key, value)------->LineRecordWriter.write(key,
value)
------->DataOutputStream.write(key, value).

DataOutputStream是内置于LineRecordWriter的作为里面的变量存在的。这样从Reduce末尾阶段到Output的过程也全然打通了。以下能够看看这上面涉及的完整的类目关系。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQW5kcm9pZGx1c2hhbmdkZXJlbg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

下一阶段的学习,可能或偏向于Task。Job阶段的过程分析,更加宏观过程上的一个分析。也可能会分析某个功能块的实现过程,比方Hadoop的IPC过程。据说用了非常多JAVA NIO的东西。

OutputFormat输出过程的学习的更多相关文章

  1. hadoop学习;自己定义Input/OutputFormat;类引用mapreduce.mapper;三种模式

    hadoop切割与读取输入文件的方式被定义在InputFormat接口的一个实现中.TextInputFormat是默认的实现,当你想要一次获取一行内容作为输入数据时又没有确定的键.从TextInpu ...

  2. hadoop 学习笔记:mapreduce框架详解

    开始聊mapreduce,mapreduce是hadoop的计算框架,我学hadoop是从hive开始入手,再到hdfs,当我学习hdfs时候,就感觉到hdfs和mapreduce关系的紧密.这个可能 ...

  3. Hadoop学习笔记:MapReduce框架详解

    开始聊mapreduce,mapreduce是hadoop的计算框架,我学hadoop是从hive开始入手,再到hdfs,当我学习hdfs时候,就感觉到hdfs和mapreduce关系的紧密.这个可能 ...

  4. Dynamic CRM 2013学习笔记(九)CrmFetchKit.js介绍:Fetchxml、多表联合查询, 批量更新

    CrmFetchKit.js是一个跨浏览器的一个类库,允许通过JavaScript来执行fetch xml的查询,还可以实现批量更新,分页查询等.目前已支持Chrome 25, Firefox 19 ...

  5. Dynamic CRM 2013学习笔记(十五)报表设计:报表入门、开发工具及注意事项

    本文是关于CRM 2013报表开发入门介绍,包括开发工具的使用,以及不同于普通Reporting service的相关注意事项. 一.CRM报表简介 报表有两种,SQL-based报表和Fetch-b ...

  6. mahout分类学习和遇到的问题总结

    这段时间学习Mahout有喜有悲.在这里首先感谢樊哲老师的指导.以下列出关于这次Mahout分类的学习和遇到的问题,还请大家多多提出建议:(全部文件操作都使用是在hdfs上边进行的). (本人用的环境 ...

  7. Android 学习笔记之如何实现简单相机功能

    PS:看来算法和数据结构还是非常有用的,以后每天都练习两道算法题目...这次忘了对代码进行折叠了..导致篇幅过长... 学习内容: 1.Android如何实现相机功能... 2.如何实现音频的录制.. ...

  8. Hadoop学习笔记(7) ——高级编程

    Hadoop学习笔记(7) ——高级编程 从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成 ...

  9. 【hadoop代码笔记】Mapreduce shuffle过程之Map输出过程

    一.概要描述 shuffle是MapReduce的一个核心过程,因此没有在前面的MapReduce作业提交的过程中描述,而是单独拿出来比较详细的描述. 根据官方的流程图示如下: 本篇文章中只是想尝试从 ...

随机推荐

  1. Codeforces Round #545 (Div. 1) 简要题解

    这里没有翻译 Codeforces Round #545 (Div. 1) T1 对于每行每列分别离散化,求出大于这个位置的数字的个数即可. # include <bits/stdc++.h&g ...

  2. cf711D. Directed Roads(环)

    题意 题目链接 \(n\)个点\(n\)条边的图,有多少种方法给边定向后没有环 Sol 一开始傻了,以为只有一个环...实际上N个点N条边还可能是基环树森林.. 做法挺显然的:找出所有的环,设第\(i ...

  3. Vue.js之生命周期

    有时候,我们需要在实例创建过程中进行一些初始化的工作,以帮助我们完成项目中更复杂更丰富的需求开发,针对这样的需求,Vue提供给我们一系列的钩子函数. vue生命周期 beforeCreate 在实例初 ...

  4. 【代码笔记】iOS-4个可以单独点击的button

    一,效果图. 二,工程图. 三,代码. ViewController.m #import "ViewController.h" @interface ViewController ...

  5. html5+css3图片旋转特效

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...

  6. idea 提交 Push rejected: Push to origin/master was rejected

    idea中,发布项目到码云上,当时按照这样的流程添加Git,然后push,提示:push to origin/master war rejected". 解决方案如下: 1.切换到自己项目所 ...

  7. js之面向对象

    本文的面向对象分为ES6和ES6之前的,重点学习ES6的===============================一.面向对象 1.什么是面向对象 a)什么是对象:万物都可以抽象成对象 时间对象 ...

  8. Python 排错UnicodeEncodeError 'ascii' codec can't encode character 错误解决方法

    Python UnicodeEncodeError 'ascii' codec can't encode character 错误解决方法   by:授客 QQ:1033553122 错误描述: py ...

  9. Laravel安装教程

    1.Call to undefined function Illuminate\Encryption\openssl_cipher_iv_length() 报这个错是因为Apache/bin目录下 l ...

  10. python 遇到的一些坑

    lst = [1, 2, 4] print lst.__iter__().next() # 打印出来的是 1 print lst.__iter__().next() # 打印出来的是 1 # 调用__ ...