Mapreduce实例——Map端join

 

实验目的

1.熟练掌握Map端join的程序编写

2.准确理解Map端join的设计原理

3.了解Map端join的适用场景

4.学会编写Map端join的程序代码解决实际问题

实验原理

MapReduce提供了表连接操作其中包括Map端join、Reduce端join还有单表连接,现在我们要讨论的是Map端join,Map端join是指数据到达map处理函数之前进行合并的,效率要远远高于Reduce端join,因为Reduce端join是把所有的数据都经过Shuffle,非常消耗资源。

1.Map端join的使用场景:一张表数据十分小、一张表数据很大。

Map端join是针对以上场景进行的优化:将小表中的数据全部加载到内存,按关键字建立索引。大表中的数据作为map的输入,对map()函数每一对<key,value>输入,都能够方便地和已加载到内存的小数据进行连接。把连接结果按key输出,经过shuffle阶段,reduce端得到的就是已经按key分组并且连接好了的数据。

为了支持文件的复制,Hadoop提供了一个类DistributedCache,使用该类的方法如下:

(1)用户使用静态方法DistributedCache.addCacheFile()指定要复制的文件,它的参数是文件的URI(如果是HDFS上的文件,可以这样:hdfs://namenode:9000/home/XXX/file,其中9000是自己配置的NameNode端口号)。JobTracker在作业启动之前会获取这个URI列表,并将相应的文件拷贝到各个TaskTracker的本地磁盘上。

(2)用户使用DistributedCache.getLocalCacheFiles()方法获取文件目录,并使用标准的文件读写API读取相应的文件。

2.本实验Map端Join的执行流程

(1)首先在提交作业的时候先将小表文件放到该作业的DistributedCache中,然后从DistributeCache中取出该小表进行join连接的 <key ,value>键值对,将其解释分割放到内存中(可以放大Hash Map等等容器中)。

(2)要重写MyMapper类下面的setup()方法,因为这个方法是先于map方法执行的,将较小表先读入到一个HashMap中。

(3)重写map函数,一行行读入大表的内容,逐一的与HashMap中的内容进行比较,若Key相同,则对数据进行格式化处理,然后直接输出。

(4)map函数输出的<key,value >键值对首先经过一个suffle把key值相同的所有value放到一个迭代器中形成values,然后将<key,values>键值对传递给reduce函数,reduce函数输入的key直接复制给输出的key,输入的values通过增强版for循环遍历逐一输出,循环的次数决定了<key,value>输出的次数。

实验环境

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_items1,orders1表记录了用户购买商品的下单数据,order_items1表记录了商品id,订单id以及明细id,它们的表结构以及关系如下图所示:

 

它们的数据内容是以"\t"键分割,数据内容如下:

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  

要求用Map端Join来进行多表连接,查询在2011-12-15日该电商都有哪些用户购买了什么商品。这里我们假设orders1文件记录数很少,order_items1文件记录数很多。

结果数据如下:

  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/mapreduce5目录。

  1. mkdir -p /data/mapreduce5  

 

3.在Linux中切换到/data/mapreduce5目录下,用wget命令从http://192.168.1.100:60000/allfiles/mapreduce5/orders1和http://192.168.1.100:60000/allfiles/mapreduce5/order_items1网址上下载文本文件orders1,order_items1。

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

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

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

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

  1. tar zxvf hadoop2lib.tar.gz  

4.首先在HDFS上新建/mymapreduce5/in目录,然后将Linux本地/data/mapreduce5目录下的orders1和order_items1文件导入到HDFS的/mymapreduce5/in目录中。

  1. hadoop fs -mkdir -p /mymapreduce5/in  
  2. hadoop fs -put /data/mapreduce5/orders1 /mymapreduce5/in  
  3. hadoop fs -put /data/mapreduce5/order_items1 /mymapreduce5/in  

 

5.新建Java Project项目,项目名为mapreduce5。

 

在mapreduce5项目下新建包,包名为mapduce。

在mapreduce包下新建类,类名为MapJoin。

 

6.添加项目所需依赖的jar包,右键项目,新建一个文件夹,命名为hadoop2lib,用于存放项目所需的jar包。

将/data/mapreduce5目录下,hadoop2lib目录中的jar包,拷贝到eclipse中mapreduce5项目的hadoop2lib目录下。

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

 

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

Map端join适用于一个表记录数很少(100条),另一表记录数很多(像几亿条)的情况,我们把小表数据加载到内存中,然后扫描大表,看大表中记录的每条join key/value是否能在内存中找到相同的join key记录,如果有则输出结果。这样避免了一种数据倾斜问题。Mapreduce的Java代码分为两个部分:Mapper部分,Reduce部分。

Mapper代码

  1. public static class MyMapper extends Mapper<Object, Text, Text, Text>{  
  2.         private Map<String, String> dict = new HashMap<>();  
  3.   
  4.         @Override  
  5.         protected void setup(Context context) throws IOException,  
  6.                 InterruptedException {  
  7.             String fileName = context.getLocalCacheFiles()[0].getName();  
  8.             System.out.println(fileName);  
  9.             BufferedReader reader = new BufferedReader(new FileReader(fileName));  
  10.             String codeandname = null;  
  11.             while (null != ( codeandname = reader.readLine() ) ) {  
  12.                 String str[]=codeandname.split("\t");  
  13.                 dict.put(str[0], str[2]+"\t"+str[3]);  
  14.             }  
  15.             reader.close();  
  16.         }  
  17.         @Override  
  18.         protected void map(Object key, Text value, Context context)  
  19.                 throws IOException, InterruptedException {  
  20.             String[] kv = value.toString().split("\t");  
  21.             if (dict.containsKey(kv[1])) {  
  22.                 context.write(new Text(kv[1]), new Text(dict.get(kv[1])+"\t"+kv[2]));  
  23.             }  
  24.         }  
  25.     }  

该部分分为setup方法与map方法。在setup方法中首先用getName()获取当前文件名为orders1的文件并赋值给fileName,然后用bufferedReader读取内存中缓存文件。在读文件时用readLine()方法读取每行记录,把该记录用split("\t")方法截取,与order_items文件中相同的字段str[0]作为key值放到map集合dict中,选取所要展现的字段作为value。map函数接收order_items文件数据,并用split("\t")截取数据存放到数组kv[]中(其中kv[1]与str[0]代表的字段相同),用if判断,如果内存中dict集合的key值包含kv[1],则用context的write()方法输出key2/value2值,其中kv[1]作为key2,其他dict.get(kv[1])+"\t"+kv[2]作为value2。

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.     for (Text text : values) {  
  6.     context.write(key, text);  
  7.     }  
  8.     }  
  9.     }  

map函数输出的<key,value >键值对首先经过一个suffle把key值相同的所有value放到一个迭代器中形成values,然后将<key,values>键值对传递给reduce函数,reduce函数输入的key直接复制给输出的key,输入的values通过增强版for循环遍历逐一输出。

完整代码

  1. package mapreduce;  
  2. import java.io.BufferedReader;  
  3. import java.io.FileReader;  
  4. import java.io.IOException;  
  5. import java.net.URI;  
  6. import java.net.URISyntaxException;  
  7. import java.util.HashMap;  
  8. import java.util.Map;  
  9. import org.apache.hadoop.fs.Path;  
  10. import org.apache.hadoop.io.Text;  
  11. import org.apache.hadoop.mapreduce.Job;  
  12. import org.apache.hadoop.mapreduce.Mapper;  
  13. import org.apache.hadoop.mapreduce.Reducer;  
  14. import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;  
  15. import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;  
  16. public class MapJoin {  
  17.   
  18.     public static class MyMapper extends Mapper<Object, Text, Text, Text>{  
  19.         private Map<String, String> dict = new HashMap<>();  
  20.   
  21.         @Override  
  22.         protected void setup(Context context) throws IOException,  
  23.                 InterruptedException {  
  24.             String fileName = context.getLocalCacheFiles()[0].getName();  
  25.             //System.out.println(fileName);  
  26.             BufferedReader reader = new BufferedReader(new FileReader(fileName));  
  27.             String codeandname = null;  
  28.             while (null != ( codeandname = reader.readLine() ) ) {  
  29.                 String str[]=codeandname.split("\t");  
  30.                 dict.put(str[0], str[2]+"\t"+str[3]);  
  31.             }  
  32.             reader.close();  
  33.         }  
  34.         @Override  
  35.         protected void map(Object key, Text value, Context context)  
  36.                 throws IOException, InterruptedException {  
  37.             String[] kv = value.toString().split("\t");  
  38.             if (dict.containsKey(kv[1])) {  
  39.                 context.write(new Text(kv[1]), new Text(dict.get(kv[1])+"\t"+kv[2]));  
  40.             }  
  41.         }  
  42.     }  
  43.     public static class MyReducer extends Reducer<Text, Text, Text, Text>{  
  44.         @Override  
  45.         protected void reduce(Text key, Iterable<Text> values, Context context)  
  46.     throws IOException, InterruptedException {  
  47.     for (Text text : values) {  
  48.     context.write(key, text);  
  49.     }  
  50.     }  
  51.     }  
  52.   
  53.     public static void main(String[] args) throws ClassNotFoundException, IOException, InterruptedException, URISyntaxException {  
  54.     Job job = Job.getInstance();  
  55.     job.setJobName("mapjoin");  
  56.     job.setJarByClass(MapJoin.class);  
  57.   
  58.     job.setMapperClass(MyMapper.class);  
  59.     job.setReducerClass(MyReducer.class);  
  60.   
  61.     job.setOutputKeyClass(Text.class);  
  62.     job.setOutputValueClass(Text.class);  
  63.   
  64.     Path in = new Path("hdfs://localhost:9000/mymapreduce5/in/order_items1");  
  65.     Path out = new Path("hdfs://localhost:9000/mymapreduce5/out");  
  66.     FileInputFormat.addInputPath(job, in);  
  67.     FileOutputFormat.setOutputPath(job, out);  
  68.   
  69.     URI uri = new URI("hdfs://localhost:9000/mymapreduce5/in/orders1");  
  70.     job.addCacheFile(uri);  
  71.   
  72.     System.exit(job.waitForCompletion(true) ? 0 : 1);  
  73.     }  
  74.     }  

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

 

9.待执行完毕后,进入命令模式下,在HDFS上/mymapreduce5/out中查看实验结果。

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

 

 

posted @ 2021-12-02 11:16  yasai  阅读(78)  评论(0编辑  收藏  举报