【MapReduce】基础案例 ---- 自定义InputFormat实现类(小文件合并成SequenceFile文件)
原创
©著作权归作者所有:来自51CTO博客作者阿呆小记的原创作品,请联系作者获取转载授权,否则将追究法律责任
文章目录
自定义InputFormat 实现类
无论HDFS还是MapReduce,在处理小文件时效率都非常低,但又难免面临处理大量小文件的场景,此时,就需要有相应解决方案。可以自定义InputFormat实现小文件的合并。
SequenceFile文件是Hadoop用来存储二进制形式的key-value对
的文件格式。
SequenceFile里面存储着多个文件,存储的形式:文件路径+名称为key
,文件内容为value
。
自定义InputFormat实现类基本步骤
返回顶部
案例:将多个小文件合并成一个SequenceFile文件
※ 分析
返回顶部
※ 代码实现
• WholeFileInputformat
- 自定义Inputformat类继承FileInputFormat,重写 createRecordReader() 方法。创建自定义recordReader对象并初始化。
- 重写isSplitable()方法,返回false,整体文件不可分割
public class WholeFileInputformat extends FileInputFormat<Text, BytesWritable> {
/**
* 重写 createRecordReader() 方法
* @param split
* @param context
* @return
* @throws IOException
* @throws InterruptedException
*/
@Override
public RecordReader<Text, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
// 创建对象
WholeRecordReader recordReader = new WholeRecordReader();
// 初始化
recordReader.initialize(split,context);
return recordReader;
}
/**
* 重写isSplitable()方法,整体文件不可分割
* @param context
* @param filename
* @return
*/
@Override
protected boolean isSplitable(JobContext context, Path filename) {
return false;
}
}
返回顶部
• WholeRecordReader
- 创建recordReader类,进行改写,实现一次读取一整个文件数据并封装到kv键值对中
- 获取文件路径和文件名封装到K中
- 获取一整个文件内容,封装到V中(path -> IO流 -> buf -> v)
package 第三章_MR框架原理.InputFormat数据输入;
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.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 java.io.IOException;
public class WholeRecordReader extends RecordReader<Text, BytesWritable> {
/// **输入文件的一部分。
// 由{@link * InputFormat#getSplits(JobContext)}返回,
// 并传递给* {@link InputFormat#createRecordReader(InputSplit,TaskAttemptContext)}。 * /
FileSplit split;
Configuration configuration;
Text k = new Text();
BytesWritable v = new BytesWritable();
// 定义标记位 --- 有内容,正在读取
boolean isProgress = true;
/**
* 初始化
* @param split
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
public void initialize(InputSplit split, TaskAttemptContext context) throws IOException, InterruptedException {
// 初始化
this.split = (FileSplit) split;
// 获取配置信息
configuration = context.getConfiguration();
}
@Override
public boolean nextKeyValue() throws IOException, InterruptedException {
// 如果有内容开始读取
if (isProgress) {
// 核心业务逻辑处理 --- 封装文件
// 1. 获取fs文件系统对象
// 1.1 通过切片获取路径
Path path = split.getPath();
// 1.2 通过文件路径获取文件系统
FileSystem fs = path.getFileSystem(configuration);
// 2.获取输入流
FSDataInputStream fis = fs.open(path);
// 3.拷贝
// 将fis中的内容读到buf中去
byte[] buf = new byte[(int) split.getLength()];
IOUtils.readFully(fis, buf, 0, buf.length);
// 4.封装 V
v.set(buf, 0, buf.length);
// 5.封装 K
k.set(path.toString());
// 6.关闭资源
IOUtils.closeStream(fis);
// 7.读取过程结束
isProgress = false;
return true;
}
return false;
}
// 获取当前k --- 当前文件路径、名称
@Override
public Text getCurrentKey() throws IOException, InterruptedException {
return k;
}
// 获取当前value --- 当前文件内容
@Override
public BytesWritable getCurrentValue() throws IOException, InterruptedException {
return v;
}
// 获取进程 --- 进度
@Override
public float getProgress() throws IOException, InterruptedException {
return 0;
}
// 关闭资源
@Override
public void close() throws IOException {
}
}
返回顶部
• Mapper阶段
public class SequenceFileMapper extends Mapper<Text, BytesWritable,Text, BytesWritable> {
// 重写 map 方法
@Override
protected void map(Text key, BytesWritable value, Context context) throws IOException, InterruptedException {
// 直接输出
context.write(key,value);
}
}
返回顶部
•Reducer阶段
public class SequenceFileReducer extends Reducer<Text, BytesWritable,Text,BytesWritable> {
// 重写 reduce 方法
@Override
protected void reduce(Text key, Iterable<BytesWritable> values, Context context) throws IOException, InterruptedException {
// 循环写出
for (BytesWritable value:values){
context.write(key,value);
}
}
}
返回顶部
• Driver阶段
- 设置输入的inputFormat
job.setInputFormatClass(WholeFileInputformat.class);
- 设置输出的outputFormat
job.setOutputFormatClass(SequenceFileOutputFormat.class);
package 第三章_MR框架原理.InputFormat数据输入;
import org.apache.hadoop.conf.Configuration;
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.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.SequenceFileOutputFormat;
public class SequenceFileDriver {
public static void main(String[] args) {
Job job = null;
Configuration conf = new Configuration();
try {
// 1.获取job对象
job = Job.getInstance(conf);
// 2.配置
job.setMapperClass(SequenceFileMapper.class);
job.setReducerClass(SequenceFileReducer.class);
job.setJarByClass(SequenceFileDriver.class);
job.setMapOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(BytesWritable.class);
// 3.设置输入的inputFormat
job.setInputFormatClass(WholeFileInputformat.class);
// 4.设置输出的outputFormat
job.setOutputFormatClass(SequenceFileOutputFormat.class);
// 5.设置输入输出路径
FileInputFormat.setInputPaths(job, new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第三章_MR框架原理\\InputFormat数据输入\\自定义InputFormat"));
FileOutputFormat.setOutputPath(job, new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第三章_MR框架原理\\InputFormat数据输入\\自定义InputFormat\\自定义InputFormatoutput\\"));
// 6.提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
更改
// 3.设置输入的inputFormat
job.setInputFormatClass(WholeFileInputformat.class);
// 4.设置输出的outputFormat
job.setOutputFormatClass(SequenceFileOutputFormat.class);
运行结果
由图可以看出,SequenceFile里面存储着多个文件,存储的形式为文件路径+名称为key
,文件内容为value
。
- 每操作一个小文件就会生成一个MapTask,最终将所有Task的结果发动到Reduce端进行合并处理!!!
返回顶部
※ DeBug调试
① Driver阶段开始运行
② 首先进入自定义RecordReader类中的nextKeyValue()方法中,依次执行,读取封装第一个文件。
③ 接着进入MapTask继续进行判断,最后返回true值。
④ 接着又是一系列判断之后,进入Mapper阶段开始写入
⑤ 写入一个文件之后,紧接着会再次进入自定义的RecordReader类,读取下一个文件。也就是说map()在没有全部执行完之前,是不会执行reduce()的。
⑥ 每读取一个文件,就会开启一个MapTask,当所有的MapTask都运行结束之后,就会进入Reducer阶段,输出全部的MapTask结果。
返回顶部