如果Mapper输出的一些Key特别多,另一些Key特别少就会产生数据倾斜。造成一些Reducer特别忙,一些则比较闲。
那么要如何解决这个问题呢?
先来考虑一下我们为什么要把数据发给Reducer。因为我们需要把id相同的放在一起才能进行拼接,所以才需要Reducer。如果我们不需要Reducer就能做拼接,就不存在数据倾斜了。
为此,我们需要为每一个MapTask准备一个表的全表。这种机制叫做Map Side Join。当然这个表的全表不能很大。
如何将数据传给MapTask
容器只有运行起来才有编号,我们也不知道会在哪里启动MapTask,因此必须想一个办法来将文件传给MapTask。Hadoop提供了一个Distributed Cache机制,能把文件在合适的时候发给MapTask。MapTask就可以从本地进行加载表数据。
代码实现
package tech.mrbcy.bigdata.mr.mapsidejoin;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Mapper.Context;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class MapSideJoin {
static class MapSideJoinMapper extends Mapper<LongWritable, Text, Text, NullWritable>{
private Map<String,String> pdInfoMap = new HashMap<String, String>();
// 启动之前会调用这个,可以在这里加载文件
@Override
protected void setup(Context context) throws IOException,
InterruptedException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("product.txt")));
String line;
while(StringUtils.isNotEmpty(line = br.readLine())){
String[] fields = line.split(",");
pdInfoMap.put(fields[0], line);
}
br.close();
}
@Override
protected void map(LongWritable key, Text value, Context context)
throws IOException, InterruptedException {
// 我们已经有完整的产品表了,所以在Map里面Join就行了
String orderLine = value.toString();
String[] orderFields = orderLine.split(",");
String product = pdInfoMap.get(orderFields[2]);
Text out = new Text(product + "," + orderLine);
context.write(out, NullWritable.get());
};
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
job.setJarByClass(MapSideJoinMapper.class);
job.setMapperClass(MapSideJoinMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
// 指定需要缓存一个文件到所有的maptask运行节点工作目录
// job.addArchiveToClassPath(archive); 缓存jar包到ClassPath下面
// job.addCacheArchive(uri); 缓存jar包到工作目录
// job.addFileToClassPath(file); 缓存普通文件到ClassPath
job.addCacheFile(new URI("file:/E:/apps/tmp/mapsidejoin/product.txt"));
// 不需要Reducer,设置Reducer数量为0
job.setNumReduceTasks(0);
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}