Mapreduce中的join操作
一、背景
MapReduce提供了表连接操作其中包括Map端join、Reduce端join还有半连接,现在我们要讨论的是Map端join,Map端join是指数据到达map处理函数之前进行合并的,效率要远远高于Reduce端join,因为Reduce端join是把所有的数据都经过Shuffle,非常消耗资源。
二、具体join
1、join的例子
比如我们有两个文件,分别存储 订单信息:products.txt,和 商品信息:orders.txt ,详细数据如下:
- products.txt:
//商品ID,商品名称,商品类型(数字表示,我们假设有一个数字和具体类型的映射)
p0001,xiaomi,001
p0002,chuizi,001 - orders.txt:
//订单号,时间,商品id,购买数量
1001,20170710,p0001,1
1002,20170710,p0001,3
1003,20170710,p0001,3
1004,20170710,p0002,1我们想象有多个商品,并有海量的订单信息,并且存储在多个 HDFS 块中。
xiaomi,7
chuizi,1该怎么处理? 我们分析上面我们想要的结果,商品名称和销量,这两个属性分别存放到不同的文件中,那我们就要考虑 在一个地方(mapper)读取这两个文件的数据,并把数据在一个地方(reducer)进行结合。这就是 MapReduce 中的 Join 了。
- 代码如下:
- Mapper:
public class joinMapper extends Mapper<LongWritable,Text,Text,Text> { private Text outKey=new Text();
private Text outValue=new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] split = line.split(",");
FileSplit inputSplit = (FileSplit) context.getInputSplit();
String name = inputSplit.getPath().getName();
//两个文件 在一个 mapper 中处理
//通过文件名判断是那种数据
if(name.startsWith("a")){
//取商品ID 作为 输出key 和 商品名称 作为 输出value,即 第0、1 的数据
outKey.set(split[0]);
outValue.set("product#" + split[1]);
context.write(outKey, outValue);
}else{
//取商品ID 作为 输出key 和 购买数量 作为 输出value,即 第2、3 的数据
outKey.set(split[2]);
outValue.set("order#" + split[3]);
context.write(outKey, outValue);
}
}
} Reducer
public class joinReducer extends Reducer<Text,Text,Text,Text> {
private Text outValue = new Text();
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//用来存放:商品ID、商品名称
List<String> productsList = new ArrayList<String>();
//用来存放:商品ID、购买数量
List<Integer> ordersList = new ArrayList<Integer>(); for (Text text:values){
String value = text.toString();
if(value.startsWith("product#")) {
productsList.add(value.split("#")[1]); //取出 商品名称
} else if(value.startsWith("order#")){
ordersList.add(Integer.parseInt(text.toString().split("#")[1].trim())); //取出商品的销量
}
}
int totalOrders = 0;
for (int i=0; i < productsList.size(); i++) {
System.out.println(productsList.size()); for (int j=0; j < ordersList.size(); j++) {
System.out.println(ordersList.size());
totalOrders += ordersList.get(j);
}
outValue.set(productsList.get(i) + "\t" + totalOrders );
//最后的输出是:商品ID、商品名称、购买数量
context.write(key, outValue);
} }
}App:
public class App {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "file:///"); Path path = new Path("F:\\mr\\join\\out");
FileSystem fileSystem = path.getFileSystem(conf);
if(fileSystem.isDirectory(path)){
fileSystem.delete(path,true);
}
Job job = Job.getInstance(conf);
//设置job的各种属性
job.setJobName("App"); //作业名称
job.setJarByClass(App.class); //搜索类
job.setInputFormatClass(TextInputFormat.class); //设置输入格式 job.setMapperClass(joinMapper.class);
job.setReducerClass(joinReducer.class);
//添加输入路径
FileInputFormat.addInputPath(job,new Path("F:\\mr\\join\\map"));
//设置输出路径
FileOutputFormat.setOutputPath(job,new Path("F:\\mr\\join\\out"));
//map输出类型
job.setOutputKeyClass(Text.class); //
job.setOutputValueClass(Text.class); //
job.waitForCompletion(true); }
}输出结果
p0001 xiaomi 7
p0002 chuizi 1
- Mapper:
- 代码如下:
2、 Map Join
一个数据集很大,另一个数据集很小(能够被完全放进内存中),MAPJION会把小表全部读入内存中,把小表拷贝多份分发到大表数据所在实例上的内存里,在map阶段直接 拿另 外一个表的数据和内存中表数据做匹配,由于在map是进行了join操作,省去了reduce运行的效率会高很多;
适用于关联表中有小表的情形;可以将小表分发到所有的map节点,这样,map节点就可以在本地对自己所读到的大表数据进行join并输出最终结果,可以大大提高join操作的并发度,加快处理速度。并用distributedcache机制将小表的数据分发到每一个maptask执行节点,从而每一个maptask节点可以从本地加载到小表的数据,进而在本地即可实现join
- left outer join的左表必须是大表
- right outer join的右表必须是大表
- inner join左表或右表均可以作为大表
- full outer join不能使用mapjoin;
- mapjoin支持小表为子查询,使用mapjoin时需要引用小表或是子查询时,需要引用别名;在mapjoin中,可以使用不等值连接或者使用or连接多个条件;
1.2、 Map Join事例
- product表
p0001,xiaomi,001
p0002,chuizi,001 - orders表
1001,20170710,p0001,1
1002,20170710,p0001,3
1003,20170710,p0001,3
1004,20170710,p0002,1 - 期望输出
xiaomi 1001,20170710,p0001,1
xiaomi 1002,20170710,p0001,3
xiaomi 1003,20170710,p0001,3
chuizi 1004,20170710,p0002,1 - 代码实现
- Mapper
/**
* 链接操作 map端链接
*/
public class MapJoinMapper extends Mapper<LongWritable,Text,Text,NullWritable> { private Map<String,String> pdInfoMap =new HashMap<String,String>();
private Text keyOut=new Text();
/**
* 通过阅读父类Mapper的源码,发现 setup方法是在maptask处理数据之前调用一次 可以用来做一些初始化工作
*/
@Override
protected void setup(Context context) {
try {
Configuration conf = context.getConfiguration();
FileSystem fs= null;
fs = FileSystem.get(conf);
FSDataInputStream fis = fs.open(new Path("file:/F:/mr/join/map/input/a.txt"));
//得到缓冲区阅读器
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
String line=null;
while((line=br.readLine())!=null){
String[] fields = line.split(",");
pdInfoMap.put(fields[0],fields[1]);
}
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 由于已经持有完整的产品信息表,所以在map方法中就能实现join逻辑了
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//订单信息
String orderline = value.toString();
String[] fields = orderline.split(",");
String pName = pdInfoMap.get(fields[2]);
keyOut.set(pName+"\t"+orderline);
context.write(keyOut,NullWritable.get());
}
} - App
public class MapJoinApp {
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "file:///");
Job job = Job.getInstance(conf);
//设置job的各种属性
job.setJobName("MapJoinApp"); //作业名称
job.setJarByClass(MapJoinApp.class); //搜索类
//添加输入路径
FileInputFormat.addInputPath(job,new Path("F:/mr/join/map/input/b.txt"));
//设置输出路径
FileOutputFormat.setOutputPath(job,new Path("F:/mr/join/map/output"));
job.setMapperClass(MapJoinMapper.class); //mapper类
//没有reduce
job.setNumReduceTasks(0);
job.setMapOutputKeyClass(Text.class); //
job.setMapOutputValueClass(NullWritable.class); // job.waitForCompletion(true);
}
} - 输出和期望输出一致
3、Reduce端Join
- Reduce端连接比Map端连接更为普遍,因为输入的数据不需要特定的结构,但是效率比较低,因为所有数据都必须经过Shuffle过程。
- 基本思路:
- Map端读取所有的文件,并在输出的内容里加上标示,代表数据是从哪个文件里来的。
- 在reduce处理函数中,按照标识对数据进行处理。
- 然后根据Key去join来求出结果直接输出。
- 例子
- 数据如上
- 计算过程:
- 在Map阶段,把所有数据标记成<key,value>的形式,其中key是id,value则根据来源不同取不同的形式:来源于products表的记录,value的值为"products#"+name;来源于orders的记录,value的值为"orders#"+score。
- 在reduce阶段,先把每个key下的value列表拆分为分别来自表A和表B的两部分,分别放入两个向量中。然后遍历两个向量做笛卡尔积,形成一条条最终的结果。
- 代码如下:
- Mapper
/**
* map阶段打标记
*/
public class reduceMapper extends Mapper<LongWritable,Text,Text,Text> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] fields = line.split(",");
FileSplit fileSplit = (FileSplit)context.getInputSplit();
String pathName = fileSplit.getPath().toString();
pathName=pathName.substring(27); //通过文件名判断是那种数据
if (pathName.startsWith("a")){//product数据 //System.out.println(keyOut+"\t"+valueOut);
context.write(new Text(fields[0]),new Text("product#"+fields[1])); }else if (pathName.startsWith("b")){ context.write(new Text(fields[2]),new Text("order#"+fields[0]+"\t"+fields[1]+"\t"+fields[3]));
}
}
} - Reducer
public class reduceReducer extends Reducer<Text,Text,Text,Text> {
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//存放产品信息
List<String> proInfo = new ArrayList<String>();
//存放订单信息
List<String> ordInfo = new ArrayList<String>();
for (Text text:values){
System.out.println("key="+key+" value="+text);
//将数组中的数据添加到对应的数组中去
if (text.toString().startsWith("product")){
proInfo.add(text.toString().split("#")[1]);
}else if(text.toString().startsWith("order")){
ordInfo.add(text.toString().split("#")[1]);
}
}
//获取两个数组的大小
int sizePro = proInfo.size();
int sizeOrd = ordInfo.size();
//遍历两个数组将结果写出去
for (int i=0;i<sizePro;i++){
for (int j=0;j<sizeOrd;j++){
context.write(key,new Text(proInfo.get(i)+" "+ordInfo.get(j)));
}
}
}
} - App
public class ReduceApp {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS", "file:///");
Job job = Job.getInstance(conf); Path path = new Path("F:\\mr\\join\\map/output1");
FileSystem fileSystem = path.getFileSystem(conf);
if(fileSystem.isDirectory(path)){
fileSystem.delete(path,true);
} //设置job的各种属性
job.setJobName("ReduceApp"); //作业名称
job.setJarByClass(ReduceApp.class); //搜索类
//添加输入路径
FileInputFormat.addInputPath(job,new Path("F:\\mr\\join\\map\\input"));
//设置输出路径
FileOutputFormat.setOutputPath(job,new Path("F:\\mr\\join\\map/output1")); job.setMapperClass(reduceMapper.class); //mapper类
job.setReducerClass(reduceReducer.class); //reducer类 job.setMapOutputKeyClass(Text.class); //
job.setMapOutputValueClass(Text.class); // job.waitForCompletion(true);
}
} - 输出结果
p0001 xiaomi 1003 20170710 3
p0001 xiaomi 1002 20170710 3
p0001 xiaomi 1001 20170710 1
p0002 chuizi 1004 20170710 1
- Mapper
细节:
- 当map读取源文件时,如何区分出是file1还是file2
FileSplit fileSplit = (FileSplit)context.getInputSplit();
String path = fileSplit.getPath().toString();
- 当map读取源文件时,如何区分出是file1还是file2
根据path就可以知道文件的来源咯。
Mapreduce中的join操作的更多相关文章
- SQL点滴2—重温sql语句中的join操作
原文:SQL点滴2-重温sql语句中的join操作 1.join语句 Sql join语句用来合并两个或多个表中的记录.ANSI标准SQL语句中有四种JOIN:INNER,OUTER,LEFTER,R ...
- MapReduce 实现数据join操作
前段时间有一个业务需求,要在外网商品(TOPB2C)信息中加入 联营自营 识别的字段.但存在的一个问题是,商品信息 和 自营联营标示数据是 两份数据:商品信息较大,是存放在hbase中.他们之前唯一的 ...
- MapReduce中的Join
一. MR中的join的两种方式: 1.reduce side join(面试题) reduce side join是一种最简单的join方式,其主要思想如下: 在map阶段,map函数同时读取两个文 ...
- 在MongoDB中使用JOIN操作
SQL与NoSQL最大的不同之一就是不支持JOIN,在传统的数据库中,SQL JOIN子句允许你使用普通的字段,在两个或者是更多表中的组合表中的每行数据.例如,如果你有表books和publisher ...
- 重温sql语句中的join操作
1.join语句 Sql join语句用来合并两个或多个表中的记录.ANSI标准SQL语句中有四种JOIN:INNER,OUTER,LEFTER,RIGHT,一个表或视图也可以可以和它自身做JOIN操 ...
- MapReduce中的Join算法
在关系型数据库中Join是非常常见的操作,各种优化手段已经到了极致.在海量数据的环境下,不可避免的也会碰到这种类型的需求,例如在数据分析时需要从不同的数据源中获取数据.不同于传统的单机模式,在分布式存 ...
- SQL中的join操作总结(非常好)
1.1.1 摘要 Join是关系型数据库系统的重要操作之一,SQL Server中包含的常用Join:内联接.外联接和交叉联接等.如果我们想在两个或以上的表获取其中从一个表中的行与另一个表中的行匹配的 ...
- 图解数据库中的join操作
1.所有的join都从cross join衍生而来 2.所有join图示 转自Say NO to Venn Diagrams When Explaining JOINs
- 案例-使用MapReduce实现join操作
哈喽-各位小伙伴们中秋快乐,好久没更新新的文章啦,今天分享如何使用mapreduce进行join操作. 在离线计算中,我们常常不只是会对单一一个文件进行操作,进行需要进行两个或多个文件关联出更多数据, ...
随机推荐
- (四)php连接apache ,使用php-fpm方式
上面各篇记录了编译安装lamp的各个部分,下面主要解决php和apache的连接问题.通过 php-fpm 连接. 连接前环境检查: php -v PHP 5.6.30 (cli) (built: O ...
- JMH 使用指南
简介 JMH(Java Microbenchmark Harness)是用于代码微基准测试的工具套件,主要是基于方法层面的基准测试,精度可以达到纳秒级.该工具是由 Oracle 内部实现 JIT 的大 ...
- QuantumTunnel:内网穿透服务设计
背景 最近工作中有公网访问内网服务的需求,便了解了内网穿透相关的知识.发现原理和实现都不复杂,遂产生了设计一个内网穿透的想法. 名字想好了,就叫QuantumTunnel,量子隧道,名字来源于量子纠缠 ...
- Qt 项目管理文件(.pro) 详解
项目文件目录树 在 Qt Creator 中新建一个 Widget Application 项目 samp2_1,在选择窗口基类的页面选择 QWidget 作为窗体基类,并选中"Genera ...
- 【AI测试】人工智能 (AI) 测试--开篇
人工智能测试 什么是人工智能,人工智能是怎么测试的.可能是大家一开始最想了解的.大家看图中关于人工智能的定义.通俗点来说呢,就是 让机器实现原来只有人类才能完成的任务:比如看懂照片,听懂说话,思考等等 ...
- this.$set用法
this.$set()的主要功能是解决改变数据时未驱动视图的改变的问题,也就是实际数据被改变了,但我们看到的页面并没有变化,这里主要讲this.$set()的用法,如果你遇到类似问题可以尝试下,vue ...
- No versions available for io.grpc:grpc-core:jar:[1.13.1] within specified range
No versions available for i{0}:[1.13.1] within specified range maven打包的时候报错是由于同一个jar包有多个版本导致的版本冲突 解决 ...
- 大爽Python入门教程 2-1 认识容器
大爽Python入门公开课教案 点击查看教程总目录 1 什么是容器 先思考这样一个场景: 有五个学生,姓名分别为: Alan, Bruce, Carlos, David, Emma. 需要给他们都打一 ...
- ubuntu更換清華軟件源
打开软件源的编辑sudo gedit /etc/apt/sources.list 软件源: Ubuntu--更改国内镜像源(阿里.网易.清华.中科大) 打開軟件源文件進行修改: 使用 sudo vim ...
- Spark 安装部署与快速上手
Spark 介绍 核心概念 Spark 是 UC Berkeley AMP lab 开发的一个集群计算的框架,类似于 Hadoop,但有很多的区别. 最大的优化是让计算任务的中间结果可以存储在内存中, ...