====
案例:自定义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);
}
}
====