Mapreduce实例——Reduce端join

原理

Reudce端进行Join连接是MapReduce框架进行表之间Join操作最为常见的模式。

1.ReduceJoin实现原理

1Map端的主要工作,为来自不同表(文件)的key/value对打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出。

2Reduce端的主要工作,在Reduce端以连接字段作为key的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在map阶段已经打标志)分开,最后进行笛卡尔只就ok了。

2.ReduceJoin的使用场景

Reduce端连接比Map端连接更为普遍,因为在map阶段不能获取所有需要的join字段,即:同一个key对应的字段可能位于不同map中,但是Reduce端连接效率比较低,因为所有数据都必须经过Shuffle过程。

3.本实验的ReduceJoin代码执行流程:

1Map端读取所有的文件,并在输出的内容里加上标识,代表数据是从哪个文件里来的。

2)在Reduce处理函数中,按照标识对数据进行处理。

3)然后将相同的key值进行Join连接操作,求出结果并直接输出。

环境

Linux Ubuntu 14.04

jdk-7u75-linux-x64

hadoop-2.6.0-cdh5.4.5

hadoop-2.6.0-eclipse-cdh5.4.5.jar

eclipse-java-juno-SR2-linux-gtk-x86_64

内容

现有某电商网站两张信息表,分别为订单表orders1和订单明细表order_items1orders1表记录了用户购买商品的下单日期以及订单编号,order_items1表记录了商品id,订单id以及明细id,它们的表结构以及关系如下图所示。

两表的数据内容如下:

orders1

  1. 订单ID   订单号          用户ID    下单日期  
  2. 52304   111215052630    176474  2011-12-15 04:58:21  
  3. 52303   111215052629    178350  2011-12-15 04:45:31  
  4. 52302   111215052628    172296  2011-12-15 03:12:23  
  5. 52301   111215052627    178348  2011-12-15 02:37:32  
  6. 52300   111215052626    174893  2011-12-15 02:18:56  
  7. 52299   111215052625    169471  2011-12-15 01:33:46  
  8. 52298   111215052624    178345  2011-12-15 01:04:41  
  9. 52297   111215052623    176369  2011-12-15 01:02:20  
  10. 52296   111215052622    178343  2011-12-15 00:38:02  
  11. 52295   111215052621    178342  2011-12-15 00:18:43  
  12. 52294   111215052620    178341  2011-12-15 00:14:37  
  13. 52293   111215052619    178338  2011-12-15 00:13:07  

order_items1

  1. 明细ID  订单ID   商品ID  
  2. 252578  52293   1016840  
  3. 252579  52293   1014040  
  4. 252580  52294   1014200  
  5. 252581  52294   1001012  
  6. 252582  52294   1022245  
  7. 252583  52294   1014724  
  8. 252584  52294   1010731  
  9. 252586  52295   1023399  
  10. 252587  52295   1016840  
  11. 252592  52296   1021134  
  12. 252593  52296   1021133  
  13. 252585  52295   1021840  
  14. 252588  52295   1014040  
  15. 252589  52296   1014040  
  16. 252590  52296   1019043  

要求查询在2011-12-15日该电商都有哪些用户购买了什么商品。

结果数据如下:

  1. 订单ID  用户ID   下单日期             商品ID  
  2. 52293   178338  2011-12-15 00:13:07 1016840  
  3. 52293   178338  2011-12-15 00:13:07 1014040  
  4. 52294   178341  2011-12-15 00:14:37 1010731  
  5. 52294   178341  2011-12-15 00:14:37 1014724  
  6. 52294   178341  2011-12-15 00:14:37 1022245  
  7. 52294   178341  2011-12-15 00:14:37 1014200  
  8. 52294   178341  2011-12-15 00:14:37 1001012  
  9. 52295   178342  2011-12-15 00:18:43 1023399  
  10. 52295   178342  2011-12-15 00:18:43 1014040  
  11. 52295   178342  2011-12-15 00:18:43 1021840  
  12. 52295   178342  2011-12-15 00:18:43 1016840  
  13. 52296   178343  2011-12-15 00:38:02 1021134  
  14. 52296   178343  2011-12-15 00:38:02 1021133  
  15. 52296   178343  2011-12-15 00:38:02 1014040  
  16. 52296   178343  2011-12-15 00:38:02 1019043  

步骤

1.切换到/apps/hadoop/sbin目录下,开启Hadoop

  1. cd /apps/hadoop/sbin  
  2. ./start-all.sh  

2.Linux本地新建/data/mapreduce6目录。

  1. mkdir -p /data/mapreduce6  

3.Linux中切换到/data/mapreduce6目录下,用wget命令从http://192.168.1.100:60000/allfiles/mapreduce6/orders1http://192.168.1.100:60000/allfiles/mapreduce6/order_items1网址上下载文本文件orders1order_items1

  1. cd /data/mapreduce6  
  2. wget http://192.168.1.100:60000/allfiles/mapreduce6/orders1  
  3. wget http://192.168.1.100:60000/allfiles/mapreduce6/order_items1  

然后在当前目录下用wget命令从http://192.168.1.100:60000/allfiles/mapreduce6/hadoop2lib.tar.gz网址上下载项目用到的依赖包。

  1. wget http://192.168.1.100:60000/allfiles/mapreduce6/hadoop2lib.tar.gz  

hadoop2lib.tar.gz解压到当前目录下。

  1. tar zxvf hadoop2lib.tar.gz  

4.首先在HDFS上新建/mymapreduce6/in目录,然后将Linux本地/data/mapreduce6目录下的orders1order_items1文件导入到HDFS/mymapreduce6/in目录中。

  1. hadoop fs -mkdir -p /mymapreduce6/in  
  2. hadoop fs -put /data/mapreduce6/orders1 /mymapreduce6/in  
  3. hadoop fs -put /data/mapreduce6/order_items1 /mymapreduce6/in  

5.新建Java Project项目,项目名mapreduce6

mapreduce6下新建包,包名为mapreduce

mapreduce包下新建类,类名为ReduceJoin

6.添加项目所需依赖的jar包,右键单击项目,新建一个目录,用于存放项目所需的jar包。

/data/mapreduce6目录下,hadoop2lib目录中的jar包,拷贝到eclipsemapreduce6项目的hadoop2lib目录下。

选中hadoop2lib目录下所有jar包,并添加到Build Path中。

7.编写程序代码,并描述其设计思路。

(1)Map端读取所有的文件,并在输出的内容里加上标识,代表数据是从哪个文件里来的。

(2)reduce处理函数中,按照标识对数据进行处理。

(3)然后将相同key值进行join连接操作,求出结果并直接输出。

Mapreducejoin连接分为MapJoinReduceJoin,这里是一个ReduceJoin连接。程序主要包括两部分:Map部分和Reduce部分。

Map代码

  1. public static class mymapper extends Mapper<Object, Text, Text, Text>{  
  2.         @Override  
  3.         protected void map(Object key, Text value, Context context)  
  4.                 throws IOException, InterruptedException {  
  5.             String filePath = ((FileSplit)context.getInputSplit()).getPath().toString();  
  6.             if (filePath.contains("orders1")) {  
  7.                 //获取行文本内容  
  8.                 String line = value.toString();  
  9.                 //对行文本内容进行切分  
  10.                 String[] arr = line.split("\t");  
  11.                 ///把结果写出去  
  12.                 context.write(new Text(arr[0]), new Text( "1+" + arr[2]+"\t"+arr[3]));  
  13.                 System.out.println(arr[0] + "_1+" + arr[2]+"\t"+arr[3]);  
  14.             }else if(filePath.contains("order_items1")) {  
  15.                 String line = value.toString();  
  16.                 String[] arr = line.split("\t");  
  17.                 context.write(new Text(arr[1]), new Text("2+" + arr[2]));  
  18.                 System.out.println(arr[1] + "_2+" + arr[2]);  
  19.             }  
  20.         }  
  21.     }  

Map处理的是一个纯文本文件,Mapper处理的数据是由InputFormat将数据集切分成小的数据集InputSplit,并用RecordReader解析成<key,value>对提供给map函数使用。在map函数中,首先用getPath()方法获取分片InputSplit的路径并赋值给filePathif判断filePath中如果包含goods.txt文件名,则将map函数输入的value值通过Split("\t")方法进行切分,与goods_visit文件里相同的商品id字段作为key,其他字段前加"1+"作为value。如果if判断filePath包含goods_visit.txt文件名,步骤与上面相同,只是把其他字段前加"2+"作为value。最后把<key,value>通过Contextwrite方法输出。

Reduce代码

  1. public static class myreducer extends Reducer<Text, Text, Text, Text>{  
  2.         @Override  
  3.         protected void reduce(Text key, Iterable<Text> values, Context context)  
  4.     throws IOException, InterruptedException {  
  5.     Vector<String> left  = new Vector<String>();  //用来存放左表的数据  
  6.         Vector<String> right = new Vector<String>();  //用来存放右表的数据  
  7.             //代集合数据  
  8.             for (Text val : values) {  
  9.             String str = val.toString();  
  10.             //将集合中的数据添加到对应的leftright  
  11.             if (str.startsWith("1+")) {  
  12.             left.add(str.substring(2));  
  13.             }  
  14.             else if (str.startsWith("2+")) {  
  15.             right.add(str.substring(2));  
  16.             }  
  17.             }  
  18.             //获取leftright集合的长度  
  19.             int sizeL = left.size();  
  20.             int sizeR = right.size();  
  21.             //System.out.println(key + "left:"+left);  
  22.             //System.out.println(key + "right:"+right);  
  23.             //遍历两个向量将结果写进去  
  24.             for (int i = 0; i < sizeL; i++) {  
  25.             for (int j = 0; j < sizeR; j++) {  
  26.             context.write( key, new Text(  left.get(i) + "\t" + right.get(j) ) );  
  27.             //System.out.println(key + " \t" + left.get(i) + "\t" + right.get(j));  
  28.             }  
  29.             }  
  30.             }  
  31.             }  

map函数输出的<key,value>经过shufflekey相同的所有value放到一个迭代器中形成values,然后将<key,values>键值对传递给reduce函数。reduce函数中,首先新建两个Vector集合,用于存放输入的values中以"1+"开头和"2+"开头的数据。然后用增强版for循环遍历并嵌套if判断,若判断values里的元素以1+开头,则通过substring(2)方法切分元素,结果存放到left集合中,若values里元素以2+开头,则仍利用substring(2)方法切分元素,结果存放到right集合中。最后再用两个嵌套for循环,遍历输出<key,value>,其中输入的key直接赋值给输出的key,输出的valueleft +"\t"+right

完整代码

  1. package mapreduce;  
  2. import java.io.IOException;  
  3. import java.util.Iterator;  
  4. import java.util.Vector;  
  5. import org.apache.hadoop.fs.Path;  
  6. import org.apache.hadoop.io.Text;  
  7. import org.apache.hadoop.mapreduce.Job;  
  8. import org.apache.hadoop.mapreduce.Mapper;  
  9. import org.apache.hadoop.mapreduce.Reducer;  
  10. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  11. import org.apache.hadoop.mapreduce.lib.input.FileSplit;  
  12. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  13. public class ReduceJoin {  
  14.     public static class mymapper extends Mapper<Object, Text, Text, Text>{  
  15.         @Override  
  16.         protected void map(Object key, Text value, Context context)  
  17.                 throws IOException, InterruptedException {  
  18.             String filePath = ((FileSplit)context.getInputSplit()).getPath().toString();  
  19.             if (filePath.contains("orders1")) {  
  20.                 String line = value.toString();  
  21.                 String[] arr = line.split("\t");  
  22.                 context.write(new Text(arr[0]), new Text( "1+" + arr[2]+"\t"+arr[3]));  
  23.                 //System.out.println(arr[0] + "_1+" + arr[2]+"\t"+arr[3]);  
  24.             }else if(filePath.contains("order_items1")) {  
  25.                 String line = value.toString();  
  26.                 String[] arr = line.split("\t");  
  27.                 context.write(new Text(arr[1]), new Text("2+" + arr[2]));  
  28.                 //System.out.println(arr[1] + "_2+" + arr[2]);  
  29.             }  
  30.         }  
  31.     }  
  32.     
  33.     public static class myreducer extends Reducer<Text, Text, Text, Text>{  
  34.         @Override  
  35.         protected void reduce(Text key, Iterable<Text> values, Context context)  
  36.     throws IOException, InterruptedException {  
  37.     Vector<String> left  = new Vector<String>();  
  38.         Vector<String> right = new Vector<String>();  
  39.             for (Text val : values) {  
  40.             String str = val.toString();  
  41.             if (str.startsWith("1+")) {  
  42.             left.add(str.substring(2));  
  43.             }  
  44.             else if (str.startsWith("2+")) {  
  45.             right.add(str.substring(2));  
  46.             }  
  47.             }  
  48.     
  49.             int sizeL = left.size();  
  50.             int sizeR = right.size();  
  51.             //System.out.println(key + "left:"+left);  
  52.             //System.out.println(key + "right:"+right);  
  53.             for (int i = 0; i < sizeL; i++) {  
  54.             for (int j = 0; j < sizeR; j++) {  
  55.             context.write( key, new Text(  left.get(i) + "\t" + right.get(j) ) );  
  56.             //System.out.println(key + " \t" + left.get(i) + "\t" + right.get(j));  
  57.             }  
  58.             }  
  59.             }  
  60.             }  
  61.     
  62.             public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {  
  63.             Job job = Job.getInstance();  
  64.             job.setJobName("reducejoin");  
  65.             job.setJarByClass(ReduceJoin.class);  
  66.     
  67.             job.setMapperClass(mymapper.class);  
  68.             job.setReducerClass(myreducer.class);  
  69.     
  70.             job.setOutputKeyClass(Text.class);  
  71.             job.setOutputValueClass(Text.class);  
  72.     
  73.             Path left = new Path("hdfs://localhost:9000/mymapreduce6/in/orders1");  
  74.             Path right = new Path("hdfs://localhost:9000/mymapreduce6/in/order_items1");  
  75.             Path out = new Path("hdfs://localhost:9000/mymapreduce6/out");  
  76.     
  77.             FileInputFormat.addInputPath(job, left);  
  78.             FileInputFormat.addInputPath(job, right);  
  79.             FileOutputFormat.setOutputPath(job, out);  
  80.     
  81.             System.exit(job.waitForCompletion(true) ? 0 : 1);  
  82.             }  
  83.             }  

8.ReduceJoin类文件中,右键并点击=>Run As=>Run on Hadoop选项,将MapReduce任务提交到Hadoop中。

9.待执行完毕后,进入命令模式下,在hdfs上从Java代码指定的路径中查看实验结果。

  1. hadoop fs -ls /mymapreduce6/out  
  2. hadoop fs -cat /mymapreduce6/out/part-r-00000  

猜你喜欢

转载自www.cnblogs.com/aishanyishi/p/10304846.html