Hadoop实践(三)---MapReduce中的输入和输出

来源:互联网 发布:自动分辨率软件 编辑:程序博客网 时间:2024/05/20 10:54

图中显示了MR中的高层次数据流,并表明了数据流各个部分的角色。

在输入端,在map之前执行数据分割的工作,在map中执行读取分割工作,所有的输出工作在reduce段执行,输出结果

这里写图片描述

部分情况下只需要map作业就可以了,那么数据流中没有Partition和Reduce这2个模块

1、数据输入

MR中有2个支持数据输入的类:

  • InputFormat:用于决定如何为map任务分割输入数据
  • RecordReader:读取输入数据

1-1、 InputFormat

MR中的每个作业必须通过InputFormat抽象类来约定输入数据

InputFormat必须实现三个约定:

  • 为map的输入参数key和value定义数据类型
  • 指定如何分割输入数据
  • 指定RecordReader实例从源读取数据

InputFormat类的注释和它的三个构造函数:

  • abstract InputFormat<K,V>:map输入的键值的类型定义
  • List<InoutSplit> getSplits(jobContext context):分割输入数据,转换为InputSplit列表
  • RecordReader<K,V> createRecordReader(InputSplit split, TaskAttemptContext context):创建RecordReader从作业输入中读取数据

最关键的是确定如何划分输入数据

在MR中,这种划分称为input split,这直接影响map 的并行运行。

因为每个分片有一个单独的map任务执行,如果InputFormat不能通过单个数据源创建多个input split,这将导致map任务运行缓慢(此时文件是单线程顺序处理的)

TextInputFormat类实现了InputFormat类的createRecordReader方法,但是将计算input split个数的工作交给父类(FileInputFormat)处理。

指定MR作业的InputFormat:job.setInputFormatClass(TextInputFormat.class)

FileInputFormat:

package T607;import org.apache.hadoop.fs.BlockLocation;import org.apache.hadoop.fs.FileStatus;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.mapreduce.InputSplit;import org.apache.hadoop.mapreduce.JobContext;import org.apache.hadoop.mapreduce.lib.input.FileSplit;import java.io.IOException;import java.util.ArrayList;import java.util.List;/** * Created by Promacanthus on 2017/6/7. */public class FileInputFormat {    public List<InputSplit> getSplits(JobContext job) throws IOException{  //listStatus 方法获取所有这次工作需要输入的文件        List<InputSplit> splits = new ArrayList<InputSplit>();        List<FileStatus> files = listStatus(job);        for (FileStatus file:files) {            Path path = file.getPath();            BlockLocation[] blockLocations = FileSystem.getFileBlockLocations(file,0,length);  //获取所有文件块            long splitSize = file.getBlockSize();  //这个分片的大小与文件块大小相同,每个文件可以有不同的文件块大小            while(splitsRemaining()){                splits.add(new FileSplit(path, ...));  //为每个文件块创建一个分片并加入结果集中            }        }        return splits;    }}

TextInputFormat:

import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.InputSplit;import org.apache.hadoop.mapreduce.RecordReader;import org.apache.hadoop.mapreduce.TaskAttemptContext;import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;import org.apache.hadoop.mapreduce.lib.input.LineRecordReader;import java.io.IOException;/** * Created by Promacanthus on 2017/6/7. */public class TextInputFormat extends FileInputFormat<LongWritable, Text> {  //FileInputFormat作为父类提供所有的输入切分的功能    public RecordReader<LongWritable, Text> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {  //默认的记录分割的换行符,但是可以使用自定义的textinputformat.record.delimiter来替换换行符        String delimiter = context.getConfiguration().get("textinoutformat.record.delimiter");        byte[] recordDelimiterBytes = null;        if(null != delimiter){            recordDelimiterBytes = delimiter.getBytes();        }        return new LineRecordReader(recordDelimiterBytes);    }    ...}

1-2、RecordReader

map任务中的RecordReader类用于从input split中读取数据,并将每行记录的键值对传给map任务,通常都要为每个input split创建一个任务,且每个任务都要一个独立的RecordReader用于读取input split中的数据

RecordReader类的注释和它的抽象方法:

  • abstract RecordReader<KEYIN, VALUEIN>:定义map输入的键值对类型
  • void initialize(InputSplit split, TaskAttempContext context):初始化,这可能涉及需要在一个文件中定位,并确定下一个记录的逻辑起点
  • boolean nextKeyValue():读取下一个记录的文件,并放回一个标记确定是否应达到分片结尾
  • KEYIN getCurrentKey():返回当前记录的键
  • VALUEIN getCurrentValue():返回当前记录的值
  • float getProgress():返回当前读取的进度
  • void close():关闭所有与数据源关联的资源

在InputFormat中的TextInputFormat中返回的是一个LineRecordReader来读取input split 中的数据。LineRecordreader直接扩展RecordReader类,并利用LineReader类读取input split中的一行行数据,LineRecordreader将文件中的字节偏移量作为map 的key,将文件中的每行内容作业map 的值

LineRecordreader:

import org.apache.hadoop.fs.FSDataInputStream;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapreduce.InputSplit;import org.apache.hadoop.mapreduce.RecordReader;import org.apache.hadoop.mapreduce.TaskAttemptContext;import org.apache.hadoop.mapreduce.lib.input.FileSplit;import org.apache.hadoop.util.LineReader;import java.io.IOException;/** * Created by Promacanthus on 2017/6/7. */public class LineRecordreader extends RecordReader<LongWritable, Text> {    private LineReader in;    private LongWritable key = new LongWritable();    private Text value = new Text();    public void initialize(InputSplit genericSplit, TaskAttemptContext context) throws IOException{        FileSplit split = (FileSplit) genericSplit;        //open the file and seek to the start of the split        FileSystem fileSystem = file.getFileSystem(job);        FSDataInputStream fsDataInputStream = fileSystem.open(split.getPath());  //使用分片文件打开一个输入流        fsDataInputStream.seek(start);  //定位到这个分片的起始位置        in = new LineReader(fsDataInputStream,job);  //创建一个LineReader这样可以从流中读取每一行        if(notAtStartOfFile){            start += in.readLine(...);  //如果不在文件的其起始位置,需要指出从哪里开始读取行。做到这一点的唯一方法是继续读字符,直到命中一个换行符,这样可以为map提供行输入        }    }    public boolean nextkeyvalue()throws IOException{  //初始化方法被调用后,MR框架反复调用nextkeyvalue()方法。直到它返回false,这意味着input split 结束        key.set(pos);  //设置下一个key 的起始点的偏移量        return in.readLine(value....)>0;  //读取下一行的值,如果读取超过input split的结尾,则返回false    }}

2、数据输出

MR中有2个支持数据输出的类:

  • OutputFormat:用于验证数据接收器的属性
  • RecordWriter:用于将reduce对象的输出结果导入数据接收器中

指定MR作业的OutputFormat:job.setOutputFormatClass(TextOutputFormat.class)

2-1、OutputFormat

OutputFormat:

  • abstract OutputFormat<K,V>:定义reduce输入键和值的类型
  • RecordWriter<K,V> getRecordWriter(TaskAttemptContext context):创建一个RecordWriter实例将数据写入到目标
  • void checkOutputSpecs(JobContext context):验证与MR作业相关联的输出信息是否正确
  • OutputCommitter getOutputCommitter(TaskAttemptContext context):回去相关OutputCommitter,当所有任务成功完成,OutputCommitter复制在最后将结果输出

TextOutputFormat扩展了FileOutputFormat,这个类主要用于处理output committing

TextOutputFormat

import org.apache.hadoop.fs.FSDataOutputStream;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.mapreduce.RecordWriter;import org.apache.hadoop.mapreduce.TaskAttemptContext;import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;import java.io.IOException;/** * Created by Promacanthus on 2017/6/7. */public class TextOutputFormat extends FileOutputFormat<K,V> {    public RecordWriter<K,V> getRecordWriter(TaskAttemptContext job) throws IOException,InterruptedException{        boolean isCompressed = getCompressOutput(job);        String keyValueSpearator = conf.get("mapred.textoutputformat.separator","\t");  //默认的键值对的分隔符是tab字符,在这里可以通过更改mapred.textoutputformat.separator的设置来改变它        Path file = getDefaultWorkFile(job,extension);  //在临时目录中为reducer创建一个唯一的文件名        FileSystem fs = file.getFileSystem(conf);        FSDataOutputStream fsDataOutputStream = fs.create(file,false);  //创建输出文件        return new LineRecordWriter<K,V>(fsDataOutputStream,keyValueSpearator);  //返回一个用于写入文件的RecordWriter    }}

2-2、 RecordWriter

LineRecordWriter返回一个LineRecordWriter对象(LineRecordWriter是LineRecordWriter的内部类)去执行写入操作

LineRecordWriter

import org.apache.hadoop.mapreduce.RecordWriter;import java.io.DataOutputStream;import java.io.IOException;/** * Created by Promacanthus on 2017/6/7. */public static class LineRecordWriter<K,V> extends RecordWriter<K,V> {    protected DataOutputStream out;    public synchronized void write(K key, V value) throws IOException{  //输出键、分隔符、值以及换行符        writeObjct(key);        out.write(keyValueSpearator);        writeObjecr(value);        out.write(newline);    }    private void writeObjct(Object o) throws IOException{  //对输出流输出对象        out.write(0);    }}

map端的InputFormat决定了有多少个map任务被执行,reduce端的任务的个数由客户端设置的mapred.reduce.tasks的值决定(如果客户端没有设置,这个值从mapred-site.xml中获取,如果不存在site文件,则从mapred-default中获取)

原创粉丝点击