对于RDBMS中的Join操作,写SQL的时候要十分注意细节,稍有差池就会耗时很长造成很大的性能瓶颈,而在Hadoop中使用MapReduce框架进行Join操作时同样耗时,但是由于Hadoop的分布式设计理念的特殊性,因此对于这种Join操作也具备了一定的特殊性。
使用MapReduce实现Join操作有多种实现方式:
在Reduce端连接为最常见的模式:
Map端的主要工作:为来自不同表(文件)的key/value对打标签以区别不同来源的记录,然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出。
Reduce端的主要工作:在Reduce端以连接字段作为key的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在map阶段已经打上标志)分开,最后进行笛卡尔就可以了。
在Map端进行连接:
使用场景:一张表十分小,一张表很大。
用法:在提交作业的时候首先将小表文件放到该作业的DistributedCache中,其次从DistributedCache中取出该小表进行Join key/value解释分割放到内存中(可以放大Hash Map等容器中)。最后扫描大表,看大表中的每条记录的Join key/value值是否能够在内存中找到相同Join key记录,如果有则直接输出结果。
SemiJoin:
SemiJoin就是所谓的半连接,其实仔细一看就是Reduce Join的一个变种,就是在map端过滤掉一些数据,在网络中之传输参与连接的数据,不参与连接的数据不必在网络中传输,从而减少了shuffle的网络传输量,使整体效率提高,其他思想和Reduce Join是一模一样的。也就是将小表中参与Join的key单独抽出来通过DistributedCache分发到相关节点,然后将其取出放到内存中(可以放到HashSet中),在map阶段扫描连接表,将Join key不再内存HashSet中的记录过滤掉,让那些参与Join的记录通过shuffle传输到Reduce端进行Join操作,其他和Reduce Join都一样。
完整代码:
package lab8;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Partitioner;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class MRJoin {
public static class MR_Join_Mapper extends Mapper{
@Override
protected void map(LongWritable key, Text value,Context context)throws IOException,InterruptedException{
//获取输入文件的全路径和名称
String pathName=((FileSplit)context.getInputSplit()).getPath().toString();
if(pathName.contains("data.txt")) {
String values[]=value.toString().split("\t");
if(values.length<3) {
//data数据格式不规范,字段小于3,抛弃数据
return;
}else {
//数据格式规范,区分标识为1
TextPair tp=new TextPair(new Text(values[1]),new Text("1"));
context.write(tp, new Text(values[0]+"\t"+values[2]));
}
}
if(pathName.contains("info.txt")) {
String values[]=value.toString().split("\t");
if(values.length<2) {
//info数据格式不规范,字段小于2,抛弃数据
return;
}else {
//数据格式规范,区分标识为0
TextPair tp=new TextPair(new Text(values[0]),new Text("0"));
context.write(tp, new Text(values[1]));
}
}
}
}
public static class MR_Join_Partitioner extends Partitioner{
@Override
public int getPartition(TextPair key,Text value,int numPartition) {
return Math.abs(key.getFirst().hashCode()*127)%numPartition;
}
}
public static class MR_Join_Comparator extends WritableComparator{
public MR_Join_Comparator() {
super(TextPair.class,true);
}
public int compare(WritableComparable a, WritableComparable b) {
TextPair t1=(TextPair)a;
TextPair t2=(TextPair)b;
return t1.getFirst().compareTo(t2.getFirst());
}
}
public static class MR_Join_Reduce extends Reducer{
@Override
protected void reduce(TextPair key,Iterable values,Context context)throws IOException,InterruptedException{
Text pid=key.getFirst();
String desc=values.iterator().next().toString();
while(values.iterator().hasNext()) {
context.write(pid,new Text(values.iterator().next().toString()+"\t"+desc));
}
}
}
public static void main(String agrs[])throws IOException,InterruptedException,ClassNotFoundException{
Configuration conf=new Configuration();
GenericOptionsParser parser =new GenericOptionsParser(conf,agrs);
String[] otherArgs=parser.getRemainingArgs();
if(agrs.length<3) {
System.err.println("Usage:MRJoin
hadoop jar MRJoin.jar lab8.MRJoin /input1/data.txt /input2/info.txt /output
执行方式二:
在eclipse中本地执行(默认)确保src文件夹下面没有hdfs-site.xml和core-site.xml
配置参数:./input1/data.txt ./input2/info.txt ./output
注:input1和input2文件夹和src文件夹处在同一级,如果src文件夹下有hadoop的相关配置文件,那么eclipse会把任务提交到HDFS中运行
文件data.txt:
201001 1003 abc
201002 1005 def
201003 1006 ghi
201004 1003 jkl
201005 1004 mno
201006 1005 pqr
文件info.txt:
1003 kaka
1004 da
1005 jue
1006 zhao
运行结果:
1003 201004 jkl kaka
1003 201001 abc kaka
1004 201005 mno da
1005 201006 pqr jue
1005 201002 def jue
1006 201003 ghi zhao
map()后:
[1003 1] , 201001 abc
[1003 1] , 201004 jkl
...
[1003 0] , kaka
...
以上的三个
reduce处理过程:
以上三个
[1003 0] , kaka
[1003 1] , 201001 abc
[1003 1] , 201004 jkl
该组中desc=1003,第2和第3个数据的value会被分别遍历Mapreduce Error: Type mismatch in key from map
1.map和reduce中的输入输出格式不对。
你看一下你map的输出和reduce的输入是不是对应的,然后看看你的map和reduce里的参数和下面的是不是设置的一样。
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
2.新旧api混用。你的map()方法 没有按新api写, 结果系统不认为它是一个重载,而是一个新方法,不会被调用。
解决办法:在的map(), reduce()前面加上@Override,并按照新的API来写map(), reduce()。