13.mapReduce的join思想和实现方法
一、在mapReduce的实现过程中,我们可能遇到这样的需求:
现在有两类数据,存放在同一个文件夹下,一类是商品的订单数据,一类是商品的信息数据。
商品的订单数据格式如下:
订单号 商品号 商品单价
10001 x01 99.00
......
商品的信息数据格式如下:
商品号 商品名称
x01 iphone6plus
而现在需要
订单号 商品名称 商品单价
这样的数据格式数据,如何实现?
二、reducejoin
在map阶段,我们将商品号作为key,订单号,商品单价,商品名称,在来一个文件来源属性,文件名称来map出这样的数据:
key value
商品号 订单号、商品单价、商品名称、文件名称
在reduce阶段,我们将map根据key相同组合的数据拿出, 遍历迭代器:
1.判断如果是来自b.txt的话,去除本次key对应的商品名称。
2.如果不是,则新建一个bean,填充数据,存放到list集合。
有了集合之后,我们可以将list里面的bean对象的商品名称赋值,然后context。write()即可。
拿到文件名的api:
FileSplit inputSplit = (FileSplit) context.getInputSplit();
String fileName = inputSplit.getPath().getName();
代码:
package com.xws.join; import java.io.IOException; import java.util.ArrayList; import java.util.List; 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.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; //com.xws.reduceJoin.ReduceJoin public class ReduceJoin { public static class ReduceJoinMapper extends Mapper<LongWritable, Text, Text, JoinBean>{ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //获得每行数据 String [] fields = value.toString().split("\t"); //获得本行数据的来源的文件名称 FileSplit inputSplit = (FileSplit) context.getInputSplit(); String fileName = inputSplit.getPath().getName(); //定义bean中的数据变量,分a.txt和b.txt分别赋值 String orderid=""; String spid=""; float price=0; String spname=""; if("a.txt".equals(fileName)){ orderid = fields[0]; spid = fields[1]; price = Float.parseFloat(fields[2]); }else{ spid = fields[0]; spname = fields[1]; } context.write(new Text(spid), new JoinBean(orderid, spid, price, spname, fileName)); } } public static class ReduceJoinReducer extends Reducer<Text, JoinBean, JoinBean, NullWritable>{ @Override protected void reduce(Text key, Iterable<JoinBean> values, Context context) throws IOException, InterruptedException { //定义一个spname变量来接收该key对应的b.txt文件中的商品名称信息 String spname =""; //定义一个list来接收来自a文件的数据 List<JoinBean> list = new ArrayList<JoinBean>(); for (JoinBean joinBean : values) { if(joinBean.getFileName().equals("b.txt")){ spname=joinBean.getSpname(); }else{ //如果不是b文件中的数据,那么a文件中的商品id、商品单价、以及我们已经获取到的本key对应的商品名称来构建一个新的JoinBean //为什么不能用迭代器中的joinBean,因为迭代器中的joinBean每次都是同一个对象,只是在迭代的时候对它的每隔变量赋了新值 //如果在这里直接add到list中时,那么都是同一个对象,即迭代的第一个对象 JoinBean jb = new JoinBean(); jb.setOrderid(joinBean.getOrderid()); jb.setPrice(joinBean.getPrice()); list.add(jb); } } //对每一个a文件的数据赋商品名称的值 for (JoinBean joinBean : list) { joinBean.setSpname(spname); context.write(joinBean, NullWritable.get()); } } } public static void main(String[] args) throws Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf); job.setJarByClass(ReduceJoin.class); job.setMapperClass(ReduceJoinMapper.class); job.setReducerClass(ReduceJoinReducer.class); job.setOutputKeyClass(ReduceJoin.class); job.setOutputValueClass(NullWritable.class); job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(JoinBean.class); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); job.waitForCompletion(true); } }
package com.xws.join; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import org.apache.hadoop.io.Writable; public class JoinBean implements Writable{ private String orderid; private String spid; private float price; private String spname; private String fileName; public JoinBean() { } public JoinBean(String orderid, String spid, float price,String spname, String fileName) { super(); this.orderid = orderid; this.spid = spid; this.price = price; this.spname = spname; this.fileName = fileName; } public String getOrderid() { return orderid; } public void setOrderid(String orderid) { this.orderid = orderid; } public String getSpid() { return spid; } public void setSpid(String spid) { this.spid = spid; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public String getSpname() { return spname; } public void setSpname(String spname) { this.spname = spname; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } @Override public void readFields(DataInput in) throws IOException { this.orderid = in.readUTF(); this.spid = in.readUTF(); this.price = in.readFloat(); this.spname = in.readUTF(); this.fileName = in.readUTF(); } @Override public void write(DataOutput out) throws IOException { out.writeUTF(orderid); out.writeUTF(spid); out.writeFloat(price); out.writeUTF(spname); out.writeUTF(fileName); } @Override public String toString() { return orderid+"\t"+spname+"\t"+"price"; } }
三、mapjoin
reduce的join逻辑思想容易出现数据倾斜的的问题,如果某一类商品的数据过多,出现其他reducetask都处理完了,
它仍然在运行的情况,拖慢了整体的效率。
解决方案:
在map阶段就join,在map阶段,我们可以重写set方法,将数据保留在内存中,map阶段取出去替换我们需要的数据。
这样在reduce阶段,直接输出数据就行。(不写reduce也可以)
代码:
package com.xws.join; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; import org.apache.commons.io.IOUtils; 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.lib.input.FileInputFormat; import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat; //com.xws.join.MapJoin public class MapJoin { public static class mapJoin extends Mapper<LongWritable, Text, Text, NullWritable>{ Map<String,String> spMap = null; /** *在maptask之前,进行数据缓存记录 */ @Override protected void setup(Context context) throws IOException, InterruptedException { spMap = new HashMap<String,String>(); FileReader fileReader = new FileReader("b.txt"); BufferedReader br = new BufferedReader(fileReader); String line =null; while((line=br.readLine())!=null){ String [] fields =line.split("\t"); spMap.put(fields[0], fields[1]); } IOUtils.closeQuietly(br); IOUtils.closeQuietly(fileReader); } @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { String line =value.toString(); String [] fields = line.split("\t"); fields[1]=spMap.get(fields[1]); context.write(new Text(fields[0]+"\t"+fields[1]+"\t"+fields[2]), NullWritable.get()); } } public static void main(String[] args) throws IOException, Exception { Configuration conf = new Configuration(); Job job = Job.getInstance(conf); job.setJarByClass(MapJoin.class); job.setMapperClass(mapJoin.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(NullWritable.class); FileInputFormat.setInputPaths(job, new Path(args[0])); FileOutputFormat.setOutputPath(job, new Path(args[1])); //将小表加入分布式缓存中,hadoop会自动帮我们把这个小表分发给每一个map task进程,放在它们的本地工作目录中 job.addCacheFile(new URI("hdfs://hadoop1:9000/mapjoin/cachefile/b.txt")); job.setNumReduceTasks(0); job.waitForCompletion(true); } }

浙公网安备 33010602011771号