第8章 MapReduce的类型与格式

1 MapReduce的类型

      默认的MapReduce作业

      示例一个最简单的MapReduce程序

/**
 * 简单一个文件转移
 */
public class example1 extends Configured implements Tool {
    public int run(String[] strings) throws Exception {
        if(strings.length != 2){
            return -1;
        }

        Job job = Job.getInstance(getConf());
        job.setJarByClass(getClass());
        FileInputFormat.addInputPath(job, new Path(strings[0]));
        FileOutputFormat.setOutputPath(job, new Path(strings[1]));
        return job.waitForCompletion(true)?0:1;
    }
   
    public static void main(String[] args){
        int exitcode = ToolRunner.run(new example1(),args);
        System.exit(exitcode);
    }
}

        像上面这种,没有进行任何参数配置的MapReduce,默认参数如下:

 

job.setInputFormatClass(TextInputFormat.class);

job.setMapperClass(Mapper.class);
job.setMapOutputKeyClass(LongWritable.class);
job.setMapOutputValueClass(Text.class);

job.setPartitionerClass(HashPartitioner.class);//取key哈希与最大整数相与,对reduceNum取余。

job.setReducerClass(Reducer.class);
job.setNumReduceTasks(1);//每个reducer运行5分钟左右、且产生至少一个HDFS块的输出是比较合适的量

job.setOutputKeyClass(LongWritable.class);
job.setOutputValueClass(Text.class);
job.setOutputFormatClass(TextOutputFormat.class);

 

2、输入格式

2.1输入分片与记录

一般来说,会对输入数据先进行分片,然后每个map操作处理一个输入分片。

输入分片在java中表示为InputSplit接口:

//开发人员不必直接处理InputSplit,因为它是由InputFormat创建的。

public abstract class InputSplit {
    //分片大小用来进行排序,以便优先处理最大的分片,从而最小化作业运行时间。
    public abstract long getLength() throws IOException, InterruptedException;

//存储位置供MapReduce系统使用以便将map任务尽量放在分片数据旁边
    public abstract String[] getLocations() throws IOException, InterruptedException;
}

       (1)对于InputFormat接口:

public abstract class InputFormat<K, V> {

//客户端通过此方法计算分片.
    public abstract List<InputSplit> getSplits(JobContext var1) throws IOException, InterruptedException;
    //map任务将输入分片传给此方法获得RecodReader(相当于记录上的迭代器),map任务就是用recodReader来生成键值对,然后再传给map函数。该过程可通过下面方法看见
    public abstract RecordReader<K, V> createRecordReader(InputSplit var1, TaskAttemptContext var2) throws IOException, InterruptedException;
}



//此方法为Mapper的run方法

public void run(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
    this.setup(context);
    try {

           //通过重复调用Context上的nextKeyValue()(委托给RecodReader的同名方法)为mapper产生键值对,传给map()函数处理
        while(context.nextKeyValue()) {
            this.map(context.getCurrentKey(), context.getCurrentValue(), context);
        }
    } finally {
        this.cleanup(context);
    }

}

       (2) FileInputFornat      

       (3) CombineFileInputFormat:处理小文件。

       (4) 避免切分:

             1、将MinNumSize设置为比文件还要大的值。

             2、继承FileInputFormat具体子类,并重写isSplitable()方法把返回值设置为false;

       (5) mapper中的文件信息

            Mapper可通过Context对象上的getInputSplit()方法获取分片有关信息,该方法返回的InputSplit可以被强制转换为一个 FileSplit,用来访问文件信息。

       (6) 将整个文件作为一条记录处理(例子)

 

//演示如何将若干小文件打包成顺序文件
public class SmallFilesToSequenceFileConverter extends Configured implements Tool {
    static class SmallMapper extends Mapper<NullWritable, BytesWritable, Text,BytesWritable> {
        private Text filename;

        //初始化获取文件名
        @Override
        protected void setup(Context context) throws IOException, InterruptedException {
            InputSplit split = context.getInputSplit();
            Path path = ((FileSplit)split).getPath();
            filename = new Text(path.toString());
        }

        //将key设置为文件名,然后value不变
        @Override
        protected void map(NullWritable key, BytesWritable value, Context context) throws IOException, InterruptedException {
            context.write(filename, value);
        }
    }

    public int run(String[] strings) throws Exception {
        Job job = Job.getInstance(getConf());
        job.setInputFormatClass(WholeFileInputFormat.class);
        job.setOutputFormatClass(SequenceFileOutputFormat.class);

        job.setJarByClass(SmallFilesToSequenceFileConverter.class);
        job.setMapperClass(SmallMapper.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(BytesWritable.class);

        return job.waitForCompletion(true)?0:1;
    }
    public static void main(String[] args) throws Exception {
        int exitcode = ToolRunner.run(new SmallFilesToSequenceFileConverter(),args);
        System.exit(exitcode);
    }
}





//把整个文件作为一条记录的inputFormat
public class WholeFileInputFormat
        extends FileInputFormat<NullWritable, BytesWritable> {
    public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit inputSplit, TaskAttemptContext Context) throws IOException, InterruptedException {
        //创建特殊的RecodReader
        WholeFileRecodReader reader = new WholeFileRecodReader();
        reader.initialize(inputSplit, Context);
        return reader;
    }

    @Override
    protected boolean isSplitable(JobContext context, Path filename) {
        //取消分片
        return false;
    }

    //该RecordReader用来将整个文件读为一条数据
    private class WholeFileRecodReader extends RecordReader<NullWritable, BytesWritable> {
        private FileSplit fileSplit;
        private Configuration conf;
        private BytesWritable value = new BytesWritable();
        //用来记录记录是否被处理过
        private boolean process = false;

        public void initialize(InputSplit inputSplit, TaskAttemptContext context) {
            this.fileSplit = (FileSplit)inputSplit;
            this.conf = context.getConfiguration();
        }
        @Override
        public boolean nextKeyValue() throws IOException, InterruptedException {
            if(!process){
                byte[] contents = new byte[(int) fileSplit.getLength()];
                Path file = fileSplit.getPath();
                FileSystem fs = file.getFileSystem(conf);
                FSDataInputStream in = null;

                try{
                    in = fs.open(file);
                    IOUtils.readFully(in, contents, 0, contents.length);
                    value.set(contents,0,contents.length);
                }finally {
                    IOUtils.closeStream(in);
                }
                process = true;
                return true;
            }
            return false;
        }
        @Override
        public NullWritable getCurrentKey() throws IOException, InterruptedException {
            return NullWritable.get();
        }
        @Override
        public BytesWritable getCurrentValue() throws IOException, InterruptedException {
            return value;
        }
        @Override
        public float getProgress() throws IOException, InterruptedException {
            return this.process?1.0f:0.0f;
        }
        @Override
        public void close() throws IOException {
            //do nothing
        }
    }
}

 

2.2 文本输入

TextInputFormat、KeyValueTextInputFormat、NLineInputFormat

 

2.3 二进制输入

 

SequenceFileInputFormat、SequenceFileAsTextInputFormat

 

2.4 多个输入

有些数据源会提供相同的数据,但格式不同,因此需要对它们分别进行解析。

可以用MultipleInputs类来处理,它允许为每条输入路径指定InputFormat和Mapper.通过MultipleInputs.addInputPath(job, Path, Inputformat, MapperClass)设定

2.5 数据库输入

用于使用JDBC从数据库中读取数据用DBInputFormat。输出到数据库DBOutputFormat

另一种方式实现HDFS和关系型数据库之间的数据移动是使用sqoop

TableInputFormat用于操作存放在Hbase表中的数据。TableOutputFormat则是把MapReduce的输出写到Hbase中。

 

3 输出格式

3.1 文本输出

默认输出格式TextOutputFormat,把每条记录写为文本行。默认键值对分隔符为制表符,可修改。可使用NullWritable省略输出的键或值,这会导致无分隔符输出,便于TextInputFormat读取。

3.2 二进制输出

SequenceFileOutputFormat……

3.3 多个输出

如果用FileOutPutFormat及其子类,每个reducer输出一个文件并由分区号命名。但有时候可能需要对输出的文件名进行控制或者每个reducer输出多个文件。此时可用MultipleOutputFormat。

3.4 延迟输出

FileOuputFormat子类会产生空的输出文件,存在一个LazyOutputFormat,可以保证指定分区第一条记录输出时才真正创建文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值