原理
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表
-
订单ID 订单号 用户ID 下单日期
-
52304 111215052630 176474 2011-12-15 04:58:21
-
52303 111215052629 178350 2011-12-15 04:45:31
-
52302 111215052628 172296 2011-12-15 03:12:23
-
52301 111215052627 178348 2011-12-15 02:37:32
-
52300 111215052626 174893 2011-12-15 02:18:56
-
52299 111215052625 169471 2011-12-15 01:33:46
-
52298 111215052624 178345 2011-12-15 01:04:41
-
52297 111215052623 176369 2011-12-15 01:02:20
-
52296 111215052622 178343 2011-12-15 00:38:02
-
52295 111215052621 178342 2011-12-15 00:18:43
-
52294 111215052620 178341 2011-12-15 00:14:37
-
52293 111215052619 178338 2011-12-15 00:13:07
order_items1表
-
明细ID 订单ID 商品ID
-
252578 52293 1016840
-
252579 52293 1014040
-
252580 52294 1014200
-
252581 52294 1001012
-
252582 52294 1022245
-
252583 52294 1014724
-
252584 52294 1010731
-
252586 52295 1023399
-
252587 52295 1016840
-
252592 52296 1021134
-
252593 52296 1021133
-
252585 52295 1021840
-
252588 52295 1014040
-
252589 52296 1014040
-
252590 52296 1019043
要求用Map端Join来进行多表连接,查询在2011-12-15日该电商都有哪些用户购买了什么商品。这里我们假设orders1文件记录数很少,order_items1文件记录数很多。
结果数据如下:
-
订单ID 用户ID 下单日期 商品ID
-
52293 178338 2011-12-15 00:13:07 1016840
-
52293 178338 2011-12-15 00:13:07 1014040
-
52294 178341 2011-12-15 00:14:37 1010731
-
52294 178341 2011-12-15 00:14:37 1014724
-
52294 178341 2011-12-15 00:14:37 1022245
-
52294 178341 2011-12-15 00:14:37 1014200
-
52294 178341 2011-12-15 00:14:37 1001012
-
52295 178342 2011-12-15 00:18:43 1023399
-
52295 178342 2011-12-15 00:18:43 1014040
-
52295 178342 2011-12-15 00:18:43 1021840
-
52295 178342 2011-12-15 00:18:43 1016840
-
52296 178343 2011-12-15 00:38:02 1021134
-
52296 178343 2011-12-15 00:38:02 1021133
-
52296 178343 2011-12-15 00:38:02 1014040
-
52296 178343 2011-12-15 00:38:02 1019043
实验步骤
1.切换到/apps/hadoop/sbin目录下,开启Hadoop。
-
cd /apps/hadoop/sbin
-
./start-all.sh
2.在Linux本地新建/data/mapreduce5目录。
-
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。
-
cd /data/mapreduce5
-
wget http://192.168.1.100:60000/allfiles/mapreduce5/orders1
-
wget http://192.168.1.100:60000/allfiles/mapreduce5/order_items1
后在当前目录下用wget命令从http://192.168.1.100:60000/allfiles/mapreduce5/hadoop2lib.tar.gz网址上下载项目用到的依赖包。
-
wget http://192.168.1.100:60000/allfiles/mapreduce5/hadoop2lib.tar.gz
将hadoop2lib.tar.gz解压到当前目录下。
-
tar zxvf hadoop2lib.tar.gz
4.首先在HDFS上新建/mymapreduce5/in目录,然后将Linux本地/data/mapreduce5目录下的orders1和order_items1文件导入到HDFS的/mymapreduce5/in目录中。
-
hadoop fs -mkdir -p /mymapreduce5/in
-
hadoop fs -put /data/mapreduce5/orders1 /mymapreduce5/in
-
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代码
-
public static class MyMapper extends Mapper<Object, Text, Text, Text>{
-
private Map<String, String> dict = new HashMap<>();
-
-
@Override
-
protected void setup(Context context) throws IOException,
-
InterruptedException {
-
String fileName = context.getLocalCacheFiles()[0].getName();
-
System.out.println(fileName);
-
BufferedReader reader = new BufferedReader(new FileReader(fileName));
-
String codeandname = null;
-
while (null != ( codeandname = reader.readLine() ) ) {
-
String str[]=codeandname.split("\t");
-
dict.put(str[0], str[2]+"\t"+str[3]);
-
}
-
reader.close();
-
}
-
@Override
-
protected void map(Object key, Text value, Context context)
-
throws IOException, InterruptedException {
-
String[] kv = value.toString().split("\t");
-
if (dict.containsKey(kv[1])) {
-
context.write(new Text(kv[1]), new Text(dict.get(kv[1])+"\t"+kv[2]));
-
}
-
}
-
}
该部分分为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代码
-
public static class MyReducer extends Reducer<Text, Text, Text, Text>{
-
@Override
-
protected void reduce(Text key, Iterable<Text> values, Context context)
-
throws IOException, InterruptedException {
-
for (Text text : values) {
-
context.write(key, text);
-
}
-
}
-
}
map函数输出的<key,value >键值对首先经过一个suffle把key值相同的所有value放到一个迭代器中形成values,然后将<key,values>键值对传递给reduce函数,reduce函数输入的key直接复制给输出的key,输入的values通过增强版for循环遍历逐一输出。
完整代码
-
package mapreduce;
-
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.hadoop.fs.Path;
-
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.output.FileOutputFormat;
-
public class MapJoin {
-
-
public static class MyMapper extends Mapper<Object, Text, Text, Text>{
-
private Map<String, String> dict = new HashMap<>();
-
-
@Override
-
protected void setup(Context context) throws IOException,
-
InterruptedException {
-
String fileName = context.getLocalCacheFiles()[0].getName();
-
//System.out.println(fileName);
-
BufferedReader reader = new BufferedReader(new FileReader(fileName));
-
String codeandname = null;
-
while (null != ( codeandname = reader.readLine() ) ) {
-
String str[]=codeandname.split("\t");
-
dict.put(str[0], str[2]+"\t"+str[3]);
-
}
-
reader.close();
-
}
-
@Override
-
protected void map(Object key, Text value, Context context)
-
throws IOException, InterruptedException {
-
String[] kv = value.toString().split("\t");
-
if (dict.containsKey(kv[1])) {
-
context.write(new Text(kv[1]), new Text(dict.get(kv[1])+"\t"+kv[2]));
-
}
-
}
-
}
-
public static class MyReducer extends Reducer<Text, Text, Text, Text>{
-
@Override
-
protected void reduce(Text key, Iterable<Text> values, Context context)
-
throws IOException, InterruptedException {
-
for (Text text : values) {
-
context.write(key, text);
-
}
-
}
-
}
-
-
public static void main(String[] args) throws ClassNotFoundException, IOException, InterruptedException, URISyntaxException {
-
Job job = Job.getInstance();
-
job.setJobName("mapjoin");
-
job.setJarByClass(MapJoin.class);
-
-
job.setMapperClass(MyMapper.class);
-
job.setReducerClass(MyReducer.class);
-
-
job.setOutputKeyClass(Text.class);
-
job.setOutputValueClass(Text.class);
-
-
Path in = new Path("hdfs://localhost:9000/mymapreduce5/in/order_items1");
-
Path out = new Path("hdfs://localhost:9000/mymapreduce5/out");
-
FileInputFormat.addInputPath(job, in);
-
FileOutputFormat.setOutputPath(job, out);
-
-
URI uri = new URI("hdfs://localhost:9000/mymapreduce5/in/orders1");
-
job.addCacheFile(uri);
-
-
System.exit(job.waitForCompletion(true) ? 0 : 1);
-
}
-
}
8.在MapJoin类文件中,右键并点击=>Run As=>Run on Hadoop选项,将MapReduce任务提交到Hadoop中。
9.待执行完毕后,进入命令模式下,在HDFS上/mymapreduce5/out中查看实验结果。
-
hadoop fs -ls /mymapreduce5/out
-
hadoop fs -cat /mymapreduce5/out/part-r-00000
来源:https://www.cnblogs.com/aishanyishi/p/10304839.html