MapReduce自定义部分详解

一、自定义InputFormat

案例:小文件处理,需将多个小文件合并为一个SequenceFile文件,SequenceFile里面存储着多个文件,存储的形式为文件路径+名称为key,文件内容为value(BytesWritable)。
思路:
1、自定义一个InputFormat,继承FileInputFormat<NullWritable, BytesWritable>
(1)重写isSplitable()方法

@Override
protected boolean isSplitable(JobContext context, Path filename) {
		return false;
}

(2)重写createRecordReader()方法,自定义RecordReader,继承RecordReader<NullWritable, BytesWritable>,并初始化

@Override
	public RecordReader<NullWritable, BytesWritable> createRecordReader(InputSplit split, TaskAttemptContext context)
			throws IOException, InterruptedException {
		// 1 定义一个自己的recordReader
		WholeRecordReader recordReader = new WholeRecordReader();
		
		// 2 初始化recordReader
		recordReader.initialize(split, context);
		
		return recordReader;
	}

2、接上述(2)自定义RecordReader,重写initialize()、nextKeyValue()、getCurrentKey()、getCurrentValue()方法

import java.io.IOException;
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;

public class WholeRecordReader extends RecordReader<NullWritable, BytesWritable> {
	private FileSplit split;
	private Configuration configuration;

	private BytesWritable value = new BytesWritable();
	private boolean processed = false;

	@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 (!processed) {
			// 1 定义缓存
			byte[] contents = new byte[(int) split.getLength()];

			// 2 获取文件系统
			Path path = split.getPath();
			FileSystem fs = path.getFileSystem(configuration);

			// 3 读取内容
			FSDataInputStream fis = null;
			try {
				// 3.1 打开输入流
				fis = fs.open(path);
				
				// 3.2 读取文件内容
				IOUtils.readFully(fis, contents, 0, contents.length);
				
				// 3.3 输出文件内容
				value.set(contents, 0, contents.length);
			} catch (Exception e) {

			} finally {
				IOUtils.closeStream(fis);
			}
			
			processed = 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 processed?1:0;
	}

	@Override
	public void close() throws IOException {

	}
}

3、自定义Mapper
★ 在hadoop的源码中,基类Mapper类和Reducer类中都是只包含四个方法:setup方法,cleanup方法,run方法,map方法。在run方法中调用了上面的三个方法:setup方法,map方法,cleanup方法。其中setup方法和cleanup方法默认是不做任何操作,且它们只被执行一次。但是setup方法一般会在map函数之前执行一些准备工作,如作业的一些配置信息等;cleanup方法则是在map方法运行完之后最后执行 的,该方法是完成一些结尾清理的工作,如:资源释放等。如果需要做一些配置和清理的工作,需要在Mapper/Reducer的子类中进行重写来实现相应的功能。map方法会在对应的子类中重新实现,就是我们自定义的map方法。该方法在一个while循环里面,表明该方法是执行很多次的。run方法就是每个maptask调用的方法。

这里,需要重写setup()方法和map()方法。

import java.io.IOException;

import org.apache.hadoop.fs.Path;
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;

/**
 * 最终输出为SequenceFile类型,切片路径为key,文件内容为value
 */
public class WholeMapper extends Mapper<NullWritable, BytesWritable, Text, BytesWritable>{
	
	Text k = new Text();

	@Override
	protected void setup( Context context) throws IOException, InterruptedException {
		
		FileSplit split = (FileSplit) context.getInputSplit();
		Path path = split.getPath();
		
		k.set(path.toString());
	}
	
	@Override
	protected void map(NullWritable key, BytesWritable value, Context context) throws IOException, InterruptedException {
		
		context.write(k, value);
	}
}

4、编写驱动程序
★ job.setOutputKeyClass和job.setOutputValueClas在默认情况下是同时设置map阶段和reduce阶段的输出,也就是说只有map和reduce输出是一样的时候才不会出问题。当map和reduce输出是不一样的时候就需要通过job.setMapOutputKeyClass和job.setMapOutputValueClas来设置map阶段的输出。

import java.io.IOException;

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 WholeDriver {

	public static void main(String[] args) throws Exception {
		
		Configuration configuration = new Configuration();
		
		Job job = Job.getInstance(configuration);
		
		job.setJarByClass(WholeDriver.class);
		
		job.setInputFormatClass(WholeFileInputFormat.class);
		job.setOutputFormatClass(SequenceFileOutputFormat.class);
		
		job.setMapperClass(WholeMapper.class);
		
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(BytesWritable.class);
		
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(BytesWritable.class);
		
		FileInputFormat.setInputPaths(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));
		
		boolean result = job.waitForCompletion(true);
		System.exit(result ? 0 : 1);
		
	}
}

二、自定义Mapper

(见:一、3、自定义Mapper)

三、自定义Partitioner分区

Mapreduce中会将map输出的kv对,按照相同key分组,然后分发给不同的reducetask。默认的分发规则为:根据key的hashcode%reducetask数来分发。
1、自定义一个Partitioner继承Partitioner<K2,V2>
K2和V2 对应的是map输出kv类型,重写getPartition(K2 key, V2 value, int numPartitions) 方法。
2、在驱动函数中增加自定义数据分区设置和reduce task设置

// 8 指定自定义数据分区
job.setPartitionerClass(自定义的Partitioner.class);
		
// 9 同时指定相应数量的reduce task
job.setNumReduceTasks(reduce task的数量); 

这里的reduce task的数量和分区数保持一致。

四、自定义Key.compareTo排序

需自定义bean对象,实现 WritableComparable 接口,重写compareTo方法。

★ 关于WritableComparable:
Interface WritableComparable
所有超级接口:
Comparable, Writable
所有已知的实现类:
BooleanWritable, BytesWritable, ByteWritable, DoubleWritable, FloatWritable, ID, ID, IntWritable, JobID, JobID, LongWritable, MD5Hash, NullWritable, Record, ShortWritable, TaskAttemptID, TaskAttemptID, TaskID, TaskID, Text, VIntWritable, VLongWritable

五、自定义Combiner

统计过程中对每一个maptask的输出进行局部汇总,以减小网络传输量即采用Combiner功能。Combiner运行在每一个MapTask中。
1、自定义一个Combiner类,继承Reducer<K1, V1, K1, V1>
K1和V1即为Map阶段的输出类型
2、驱动类中指定combiner

job.setCombinerClass(自定义的Combiner.class);

六、自定义Reducer

(继承Reducer)

七、自定义分组/GroupingComparator

“四、自定义Key.compareTo排序” 中 Map阶段自定义Bean的compareTo相对应,GroupingComparator为Reduce阶段的比较。
1、自定义一个GroupingComparator 继承 WritableComparator 类

★ 关于WritableComparator
所有实现的接口:
Comparator, Configurable, RawComparator
直接已知子类:
KeyFieldBasedComparator, RecordComparator

(1)定义空参构造

protected 自定义GroupingComparator() {
		super(自定义Bean.class, true);
}

(2)重写compare(WritableComparable a, WritableComparable b)方法,比如将某个值一样的视为一组。
compare()返回值为int型,return one of -1, 0, or 1 according to whether the value of expression is negative, zero or positive。

八、map端表合并(Distributedcache)

适用于关联表中有小表的情形;
可以将小表分发到所有的map节点,这样,map节点就可以在本地对自己所读到的大表数据进行合并并输出最终结果,可以大大提高合并操作的并发度,加快处理速度。
1、先在驱动模块中添加缓存文件

// 加载缓存数据
job.addCacheFile(new URI("文件路径"));

2、读取缓存的文件数据,自定义Mapper
重写setup()方法(因为只需要获取一次文件)和map()方法,举例如下

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class DistributedCacheMapper extends Mapper<LongWritable, Text, Text, NullWritable>{

	Map<String, String> pdMap = new HashMap<>();
	
	@Override
	protected void setup(Mapper<LongWritable, Text, Text, NullWritable>.Context context)
			throws IOException, InterruptedException {
		// 1 获取缓存的文件
		BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("pd.txt")));
		
		String line;
		while(StringUtils.isNotEmpty(line = reader.readLine())){
			// 2 切割
			String[] fields = line.split("\t");
			
			// 3 缓存数据到集合
			pdMap.put(fields[0], fields[1]);
		}
		
		// 4 关流
		reader.close();
	}
	
	Text k = new Text();
	
	@Override
	protected void map(LongWritable key, Text value, Context context)
			throws IOException, InterruptedException {
		// 1 获取一行
		String line = value.toString();
		
		// 2 截取
		String[] fields = line.split("\t");
		
		// 3 获取订单id
		String orderId = fields[1];
		
		// 4 获取商品名称
		String pdName = pdMap.get(orderId);
		
		// 5 拼接
		k.set(line + "\t"+ pdName);
		
		// 6 写出
		context.write(k, NullWritable.get());
	}
}

九、自定义OutputFormat

1、自定义一个outputformat,继承 FileOutputFormat<K1, V1>
K1和V1是最终要输出的类型
同时,重写getRecordWriter(TaskAttemptContext job)方法,并返回一个自定义的RecordWriter
2、自定义RecordWriter,继承RecordWriter<K1, V1>
执行具体些数据的业务,如

import java.io.IOException;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.RecordWriter;
import org.apache.hadoop.mapreduce.TaskAttemptContext;

public class FilterRecordWriter extends RecordWriter<Text, NullWritable> {
	FSDataOutputStream atguiguOut = null;
	FSDataOutputStream otherOut = null;

	public FilterRecordWriter(TaskAttemptContext job) {
		// 1 获取文件系统
		FileSystem fs;

		try {
			fs = FileSystem.get(job.getConfiguration());

			// 2 创建输出文件路径
			Path atguiguPath = new Path("e:/atguigu.log");
			Path otherPath = new Path("e:/other.log");

			// 3 创建输出流
			atguiguOut = fs.create(atguiguPath);
			otherOut = fs.create(otherPath);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void write(Text key, NullWritable value) throws IOException, InterruptedException {

		// 判断是否包含“atguigu”输出到不同文件
		if (key.toString().contains("atguigu")) {
			atguiguOut.write(key.toString().getBytes());
		} else {
			otherOut.write(key.toString().getBytes());
		}
	}

	@Override
	public void close(TaskAttemptContext context) throws IOException, InterruptedException {
		// 关闭资源
		if (atguiguOut != null) {
			atguiguOut.close();
		}
		
		if (otherOut != null) {
			otherOut.close();
		}
	}
}

3、编写Mapper和Reducer

4、编写Driver

// 要将自定义的输出格式组件设置到job中
job.setOutputFormatClass(自定义的OutputFormat.class);

需要注意,虽然我们自定义了outputformat,但是因为我们的outputformat继承自fileoutputformat,而fileoutputformat要输出一个_SUCCESS文件,所以,在这还得指定一个输出目录

FileOutputFormat.setOutputPath(job, new Path(args[1]));
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MapReduce是一种用于处理大规模数据集的编程模型和软件框架。Hadoop是一个基于MapReduce模型的分布式文件存储和处理系统。在Hadoop中,MapReduce被广泛用于数据处理和分析任务。 自定义二次排序是MapReduce中常见的一种需求,其目的是对MapReduce的输出进行排序。下面我们来介绍一下如何在Linux上使用Hadoop实现自定义二次排序。 1. 准备数据 首先我们需要准备一个数据集,假设我们有一个文本文件,每行包含两个字段,分别为学生姓名和成绩,中间用制表符分隔。例如: ``` Tom 80 Jerry 70 Mike 90 Lucy 85 ``` 2. 编写Mapper代码 自定义二次排序需要进行两次排序,第一次按照学生姓名进行排序,第二次按照成绩进行排序。因此,我们需要在Mapper中将学生姓名和成绩作为Key-Value输出。 我们可以使用TextPair类来存储学生姓名和成绩,代码如下: ``` public class SortMapper extends Mapper<LongWritable, Text, TextPair, Text> { private TextPair pair = new TextPair(); public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String[] fields = value.toString().split("\t"); pair.set(fields[0], fields[1]); context.write(pair, value); } } ``` 在这段代码中,我们首先将输入的一行数据拆分成学生姓名和成绩两个字段,然后使用TextPair类将它们作为Key输出,原始数据作为Value输出。 3. 编写Partitioner代码 Partitioner用于对Mapper的输出进行分区,以确保相同Key的数据被分配到同一个Reducer中。在自定义二次排序中,我们需要按照学生姓名进行分区,因此我们可以使用HashPartitioner来进行分区,代码如下: ``` public class SortPartitioner extends Partitioner<TextPair, Text> { public int getPartition(TextPair key, Text value, int numPartitions) { return (key.getFirst().hashCode() & Integer.MAX_VALUE) % numPartitions; } } ``` 在这段代码中,我们使用HashPartitioner将学生姓名的HashCode和Partition数取模来确定数据被分配到哪个Reducer中。 4. 编写GroupComparator代码 GroupComparator用于将相同学生姓名的数据分配到同一个Reducer中,代码如下: ``` public class SortGroupComparator extends WritableComparator { protected SortGroupComparator() { super(TextPair.class, true); } public int compare(WritableComparable a, WritableComparable b) { TextPair pair1 = (TextPair) a; TextPair pair2 = (TextPair) b; return pair1.getFirst().compareTo(pair2.getFirst()); } } ``` 在这段代码中,我们重载了compare方法,用于比较两个Key的学生姓名是否相同。 5. 编写SortComparator代码 SortComparator用于对每个Reducer中的数据进行排序,按照成绩从大到小排序,代码如下: ``` public class SortComparator extends WritableComparator { protected SortComparator() { super(TextPair.class, true); } public int compare(WritableComparable a, WritableComparable b) { TextPair pair1 = (TextPair) a; TextPair pair2 = (TextPair) b; int cmp = pair1.getFirst().compareTo(pair2.getFirst()); if (cmp != 0) { return cmp; } return -pair1.getSecond().compareTo(pair2.getSecond()); } } ``` 在这段代码中,我们首先比较两个Key的学生姓名是否相同,如果相同则比较成绩,否则直接返回姓名比较结果。 6. 编写Reducer代码 Reducer用于对Mapper的输出进行聚合和处理。在自定义二次排序中,我们只需要将每个学生的成绩按照从高到低的顺序输出即可,代码如下: ``` public class SortReducer extends Reducer<TextPair, Text, Text, Text> { public void reduce(TextPair key, Iterable<Text> values, Context context) throws IOException, InterruptedException { for (Text value : values) { context.write(key.getFirst(), value); } } } ``` 在这段代码中,我们首先输出学生姓名,然后按照原始数据的顺序输出。 7. 编写Driver代码 最后,我们需要编写Driver代码来启动MapReduce作业。代码如下: ``` public class SortDriver extends Configured implements Tool { public int run(String[] args) throws Exception { Job job = Job.getInstance(getConf()); job.setJarByClass(SortDriver.class); job.setMapperClass(SortMapper.class); job.setPartitionerClass(SortPartitioner.class); job.setGroupingComparatorClass(SortGroupComparator.class); job.setSortComparatorClass(SortComparator.class); job.setReducerClass(SortReducer.class); job.setMapOutputKeyClass(TextPair.class); job.setMapOutputValueClass(Text.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(Text.class); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); return job.waitForCompletion(true) ? 0 : 1; } public static void main(String[] args) throws Exception { int exitCode = ToolRunner.run(new SortDriver(), args); System.exit(exitCode); } } ``` 在这段代码中,我们首先创建一个Job实例,然后设置Mapper、Partitioner、GroupComparator、SortComparator和Reducer等类。最后,我们指定输入路径和输出路径,并启动作业。 以上就是在Linux上使用Hadoop实现自定义二次排序的流程。通过这个例子,您可以了解到如何在Linux系统上使用MapReduce编程模型和Hadoop分布式文件存储和处理系统来处理大规模数据集。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lmh450201598

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值