案例:自定义inputFormat合并小文件

====
案例:自定义inputFormat合并小文件


1、上传之前的合并。文件都给合并到了一起分不开,只适用于同一类型的文件
2、上传之后的合并。已经有了大量的小文件在hdfs上面了,可以通过自定义inputformat实现文件的读取,然后将文件输出成sequenceFile类型的
我们将文件转换成sequenceFile之后,我们到时候读取的时候,使用SequenceFileInputFormat来读取,这个文件就又会读取成一个个的文件
3、使用har归档文件


1.1 需求
无论hdfs还是mapreduce,对于小文件都有损效率,实践中,又难免面临处理大量小文件的场景,此时,就需要有相应解决方案

1.2 分析
小文件的优化无非以下几种方式:
1、    在数据采集的时候,就将小文件或小批数据合成大文件再上传HDFS
2、    在业务处理之前,在HDFS上使用mapreduce程序对小文件进行合并
3、    在mapreduce处理时,可采用combineInputFormat提高效率

1.3 实现
本节实现的是上述第二种方式
程序的核心机制:
自定义一个InputFormat
改写RecordReader,实现一次读取一个完整文件封装为KV
在输出时使用SequenceFileOutPutFormat输出合并文件


我们现在的需求,是将所有的小文件都读取进来,成为一个大的二进制的文件。
TextInputFormat一行一行读取数据
将每一个文件的内容,一次性读取出来,成为一个二进制的字节数字。每个文件形成一个字节数组。
k1: NullWritable; v1: BytesWritable
mapper阶段将所有的数据不做任何处理,直接输出,输出到一个sequenceFile里面去。
k2: Text, 文件的路径或者文件的名称; v2: BytesWritable
TextOutputFormat来进行输出。使用SequenceFileOutputFormat来实现将文件输出成为二进制格式。

代码实现:
package cn.itcast.demo3.mergefile;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.JobContext;
import org.apache.hadoop.mapreduce.RecordReader;
import org.apache.hadoop.mapreduce.TaskAttemptContext;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;

import java.io.IOException;

public class MyInputFormat extends FileInputFormat<NullWritable,BytesWritable> {


    /**
     * 覆写recordReader  在这里面我们需要去读取文件,将文件的所有的内容,全部读取成为一个字节数组
     * @param inputSplit
     * @param taskAttemptContext
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        MyRecorder myRecorder = new MyRecorder();
        myRecorder.initialize(inputSplit,taskAttemptContext);

        return myRecorder;
    }

    /**
     * 文件是否可切分,直接返回false不可切分,到时候读取文件的时候,就要一次性全部读取出来
     * @param context
     * @param filename
     * @return
     */
    @Override
    protected boolean isSplitable(JobContext context, Path filename) {
        return false;
    }

    //byte
    //byte[]

}


package cn.itcast.demo3.mergefile;

import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.InputSplit;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;

import java.io.IOException;

public class MyInputMappr extends Mapper<NullWritable,BytesWritable,Text,BytesWritable> {

    @Override
    protected void map(NullWritable key, BytesWritable value, Context context) throws IOException, InterruptedException {
        //获取文件名称
        FileSplit fileSplit = (FileSplit) context.getInputSplit();
        String name = fileSplit.getPath().getName();
        //直接将我们的value写出去
        context.write(new Text(name),value);

    }
}


package cn.itcast.demo3.mergefile;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.NullWritable;
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 java.io.IOException;

public class MyRecorder extends RecordReader<NullWritable,BytesWritable> {
    private FileSplit fileSplit;

    private BytesWritable bytesWritable =new BytesWritable();

    private Configuration configuration;

    private boolean  flag = false;

    /**
     * 初始化的方法,在我们程序启动之前会调用一次
     * @param inputSplit
     * @param taskAttemptContext
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public void initialize(InputSplit inputSplit, TaskAttemptContext taskAttemptContext) throws IOException, InterruptedException {
        this.fileSplit = (FileSplit) inputSplit;
        this.configuration = taskAttemptContext.getConfiguration();

    }

    /**
     * 读取数据的方法,如果返回true,表示已经读取完成,如果返回false表示没有读取完成
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {

        if(!flag){
            //读取文件的切片的内容,全部保存到BytesWritable
            FileSystem fileSystem = FileSystem.get(configuration);
            Path path = fileSplit.getPath();
            //获取到文件切片的输入流
            FSDataInputStream inputStream = fileSystem.open(path);
            //如何将输入流,放到BytesWritable里面去   可以将输入流放到  byte[]   然后将byte[]  设置到BytesWritable里面去

            //定义一个字节数组,定义自己数组的大小就是我们文件内容的大小,这样就可以一次性将我们文件的内容全部装到字节数组里面来
            byte[] arrays = new byte[(int)fileSplit.getLength()];
            //  IOUtils.readF
            IOUtils.readFully(inputStream,arrays,0,arrays.length);

            bytesWritable.set(arrays,0,arrays.length);
            //标志位重置为true,表示我们这个文件已经读取完成,去读取下一个文件即可
            flag = true;


            IOUtils.closeStream(inputStream);
            fileSystem.close();

            return  true;
        }

        return false;
    }

    /**
     * 返回我们当前读取的key1
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public NullWritable getCurrentKey() throws IOException, InterruptedException {
        return NullWritable.get();
    }

    /**
     * 返回我们读取数据得到的v1
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public BytesWritable getCurrentValue() throws IOException, InterruptedException {
        return bytesWritable;
    }

    /**
     * 读取数据的进度
     * @return
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    public float getProgress() throws IOException, InterruptedException {
        return flag?1.0f:0.0f;
    }

    /**
     * 关闭方法
     * @throws IOException
     */
    @Override
    public void close() throws IOException {

    }
}


package cn.itcast.demo3.mergefile;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.BytesWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

public class MyInputMain extends Configured implements Tool {
    @Override
    public int run(String[] args) throws Exception {
        Job job = Job.getInstance(super.getConf(), "myinputFormat");

        job.setInputFormatClass(MyInputFormat.class);
        MyInputFormat.addInputPath(job,new Path("file:///F:\\传智播客大数据离线阶段课程资料\\5、大数据离线第五天\\自定义inputformat_小文件合并\\input"));

        job.setMapperClass(MyInputMappr.class);
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(BytesWritable.class);

        /**
         * 我们这里没有reduce的程序,但是我们输出的数据类型是SequenceFile类型的
         * SequenceFile输出的数据类型,默认找的是以下这两个设置
         */
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(BytesWritable.class);

        job.setOutputFormatClass(SequenceFileOutputFormat.class);
        SequenceFileOutputFormat.setOutputPath(job,new Path("file:///F:\\传智播客大数据离线阶段课程资料\\5、大数据离线第五天\\自定义inputformat_小文件合并\\seq_out_new"));

        boolean b = job.waitForCompletion(true);


        return b?0:1;
    }

    public static void main(String[] args) throws Exception {
        int run = ToolRunner.run(new Configuration(), new MyInputMain(), args);
        System.exit(run);

    }

}
====

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值