MapReduce中的Join
- 1、SQL中的关联
- sql中多表关联方式
- join:实现多张表之间列与列的关联
- union:实现多张表之间行与行的关联
- 子查询:查询语句的嵌套
- 笛卡尔积:两张数据表,不指定关联条件
- 2、SQL中的join
- 3、MapReduce中的Join
- 需求
- 分析
- step1:结果长什么样?
- step2:有没有分组或者排序
- step3:Map输出的Value的是什么?
- step4:验证
- 4、ReduceJoin的实现
- 5、Map Jon的实现
1、SQL中的关联
- 多表关联:
实现两张表之间数据的联系
- 订单表:订单id、商品id、订单价格
- 商品表:商品id、商品名称
- 需求:得到每个订单的订单信息以及对应的商品的名称
- 前提:两张表需要有联系
商品id
sql中多表关联方式
join:实现多张表之间列与列的关联
一般用于如果结果中出现了多张表的表,就用join实现
- 订单表:订单id、商品id、订单价格
- 商品表:商品id、商品名称
- 将两张表的列进行合并
- 订单id、商品id、订单价格、商品id、商品名称
select *
from 订单表 a
join 商品表 b on a.商品id = b.商品id;
union:实现多张表之间行与行的关联
- table1:小学的学生信息
- 学生id、学生姓名、学生入学年龄……
- table2:中学的学生信息
- 学生id、学生姓名、学生入学年龄……
- 需求:实现将两张表中的数据进行合并
insert into table3
select * from table1
union
select * from table2;
子查询:查询语句的嵌套
查询结果只有一张表的列,但是查询的条件依赖于另外一张表
- 订单表:订单id、商品id、订单价格
- 商品表:商品id、商品名称
- 需求:我想查询商品名称叫做牙刷的所有订单信息
select *
from 订单表
where 商品id =(select 商品id
from 商品表
where 商品名称 = 牙刷)
笛卡尔积:两张数据表,不指定关联条件
select * from a,b;
select * from a join b;
由于没有指定条件,a的每一条关联了b的每一条
笛卡尔积的结果有多少条:a的条数*b的条数
- 订单表
1001,20150710,p0001,2
1002,20150710,p0002,3
1002,20150710,p0003,3
1003,20150710,p0003,3
- 商品表
p0001,直升机,1000,2000
p0002,坦克,1000,3000
p0003,火箭,10000,2000
p0004,装甲车,10000,2000
- 笛卡尔积:4*4= 16条数据
- 大数据中一般严禁产生笛卡尔积
- 表:1亿
- 表:2亿
- 笛卡尔积会产生1亿*2亿!!!
2、SQL中的join
- inner join:内连接:两张表都有结果才有
- outer join:外连接
- left outer join:左表有,结果就有
- right outer join :右表有,结果就有
- full join:全连接,任何一张表有,结果就有
- Mysql中不支持
- 如果对方没有,以null来补齐
--订单表
1001,20150710,p0001,2
1002,20150710,p0002,3
1002,20150710,p0003,3
1003,20150710,p0005,3
--商品表
p0001,直升机,1000,2000
p0002,坦克,1000,3000
p0003,火箭,10000,2000
p0004,装甲车,10000,2000
--full join:5条
1001,20150710,p0001,2 p0001,直升机,1000,2000
1002,20150710,p0002,3 p0002,坦克,1000,3000
1002,20150710,p0003,3 p0003,火箭,10000,2000
1003,20150710,p0005,3 null,null,null,null
null,null,null,null p0004,装甲车,10000,2000
3、MapReduce中的Join
基于文件实现关联
需求
- 订单文件:orders.txt
1001,20150710,p0001,2
1002,20150710,p0002,3
1002,20150710,p0003,3
- 商品文件:product.txt
p0001,直升机,1000,2000
p0002,坦克,1000,3000
p0003,火箭,10000,2000
基于商品和订单数据实现关联,得到每个订单的信息以及商品的名称
分析
step1:结果长什么样?
1001,20150710,p0001,2 直升机
1002,20150710,p0002,3 坦克
1002,20150710,p0003,3 火箭
reduceJoin:
p0001 直升机 1001 20150710 2
p0002 坦克 1002 20150710 3
p0003 火箭 1002 20150710 3
step2:有没有分组或者排序
没有排序
- 有没有分组呢?
- 总共 输入6条数据
- 是按照订单分组吗?
不是,因为商品信息中是没有订单的
- 要分组的字段一定是两份数据都有的字段:就是关联字段
- 按照商品id进行分组
- map输出的Key就是商品id
step3:Map输出的Value的是什么?
- 总共 输入6条数据,进入Map就是6个KV
- 需要对数据做判断
- 如果这条数据是订单数据
- value就是除了商品id以外的订单的信息
- 如果这条数据是商品数据
- value就是商品名称
step4:验证
- Input
1001,20150710,p0001,2
1002,20150710,p0002,3
1002,20150710,p0003,3
p0001,直升机,1000,2000
p0002,坦克,1000,3000
p0003,火箭,10000,2000
- Map
key value
p0001 1001,20150710,2
p0002 1002,20150710,3
p0003 1002,20150710,3
p0001 直升机
p0002 坦克
p0003 火箭
- Shuffle
分组:按照商品id进行分组
key Itrable:value
p0001 1001,20150710,2 直升机
p0002 1002,20150710,3 坦克
p0003 1002,20150710,3 火箭
- reduce
- 迭代将订单信息和商品名称拼接输出即可
4、ReduceJoin的实现
package bigdata.hanjiaxiaozhi.cn.mr.join;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
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.FileSplit;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.io.IOException;
/**
* @ClassName MRDriver
* @Description TODO 实现ReduceJoin
* @Date 2020/5/30 10:34
* @Create By hanjiaxiaozhi
*/
public class ReduceJoinMr extends Configured implements Tool {
/**
* 用于将Job的代码封装
* @param args
* @return
* @throws Exception
*/
@Override
public int run(String[] args) throws Exception {
//todo:1-构建一个Job
Job job = Job.getInstance(this.getConf(),"model");//构建Job对象,调用父类的getconf获取属性的配置
job.setJarByClass(ReduceJoinMr.class);//指定可以运行的类型
//todo:2-配置这个Job
//input
// job.setInputFormatClass(TextInputFormat.class);//设置输入的类的类型,默认就是TextInputFormat
Path inputPath1 = new Path("datas/join/orders.txt");//读取订单数据
Path inputPath2 = new Path("datas/join/product.txt");//读取商品的数据
//设置的路径可以给目录,也可以给定文件,如果给定目录,会将目录中所有文件作为输入,但是目录中不能包含子目录
TextInputFormat.setInputPaths(job,inputPath1,inputPath2);//为当前job设置输入的路径
//map
job.setMapperClass(MRMapper.class);//设置Mapper的类,需要调用对应的map方法
job.setMapOutputKeyClass(Text.class);//设置Mapper输出的key类型
job.setMapOutputValueClass(Text.class);//设置Mapper输出的value类型
//shuffle
// job.setPartitionerClass(HashPartitioner.class);//自定义分区
// job.setGroupingComparatorClass(null);//自定义分组的方式
// job.setSortComparatorClass(null);//自定义排序的方式
//reduce
job.setReducerClass(MRReducer.class);//设置Reduce的类,需要调用对应的reduce方法
job.setOutputKeyClass(Text.class);//设置Reduce输出的Key类型
job.setOutputValueClass(Text.class);//设置Reduce输出的Value类型
job.setNumReduceTasks(1);//设置ReduceTask的个数,默认为1
//output:输出目录默认不能提前存在
// job.setOutputFormatClass(TextOutputFormat.class);//设置输出的类,默认我诶TextOutputFormat
Path outputPath = new Path("datas/output/join/reduceJoin");//用程序的第三个参数作为输出
//解决输出目录提前存在,不能运行的问题,提前将目前删掉
//构建一个HDFS的文件系统
FileSystem hdfs = FileSystem.get(this.getConf());
//判断输出目录是否存在,如果存在就删除
if(hdfs.exists(outputPath)){
hdfs.delete(outputPath,true);
}
TextOutputFormat.setOutputPath(job,outputPath);//为当前Job设置输出的路径
//todo:3-提交运行Job
return job.waitForCompletion(true) ? 0:-1;
}
/**
* 程序的入口,调用run方法
* @param args
*/
public static void main(String[] args) throws Exception {
//构建一个Configuration对象,用于管理这个程序所有配置,工作会定义很多自己的配置
Configuration conf = new Configuration();
//t通过Toolruner的run方法调用当前类的run方法
int status = ToolRunner.run(conf, new ReduceJoinMr(), args);
//退出程序
System.exit(status);
}
/**
* @ClassName MRMapper
* @Description TODO 这是MapReduce模板的Map类
* 输入的KV类型:由inputformat决定,默认是TextInputFormat
* 输出的KV类型:由map方法中谁作为key,谁作为Value决定
*/
public static class MRMapper extends Mapper<LongWritable, Text, Text,Text> {
Text outputKey= new Text();
Text outputValue = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//两个文件的每一行数据,就是Value
// 要判断这条数据来自哪个文件
FileSplit fileSplit = (FileSplit) context.getInputSplit();//获取这条数据对应的分片
String name = fileSplit.getPath().getName();//获取这条数据对应的文件的名称
if("orders.txt".equals(name)){
//如果这是订单的数据:1001,20150710,p0001,2
String[] split1 = value.toString().split(",");
//用商品id作为Key
this.outputKey.set(split1[2]);
//其他信息作为Value
this.outputValue.set(split1[0]+"\t"+split1[1]+"\t"+split1[3]);
//输出
context.write(this.outputKey,this.outputValue);
}else{
//这是商品数据:p0001,直升机,1000,2000
String[] split2 = value.toString().split(",");
//用商品id作为key
this.outputKey.set(split2[0]);
//用商品名称作为Value
this.outputValue.set(split2[1]);
//输出
context.write(this.outputKey,this.outputValue);
}
}
}
/**
* @ClassName MRReducer
* @Description TODO MapReduce模板的Reducer的类
* 输入的KV类型:由Map的输出决定,保持一致
* 输出的KV类型:由reduce方法中谁作为key,谁作为Value决定
*/
public static class MRReducer extends Reducer<Text,Text,Text,Text> {
@Override
protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
//传进来的是每个商品id对应的订单信息和商品名称
StringBuilder stringBuilder = new StringBuilder();
for (Text value : values) {
//将订单信息和商品的名称进行拼接
stringBuilder.append(value.toString()+"\t");
}
//输出
context.write(key,new Text(stringBuilder.toString()));
}
}
}
- Reduce Join
- 根据两份数据的关联条件作为分组的条件
- 在Shuffle中进行了分组
- 相同id的数据进入了同一组,在Reduce中实现关联的实现
- 应用场景:大的数据join大的数据
- 必须经过shuffle的分组,会带来不必要的麻烦,做排序、会产生文件,非常慢
5、Map Jon的实现
- 应用场景:适合于小数据量 join 大数据量
- 设计思想:将小数据量的文件放到分布式内存中【每一台机器都有】,谁要谁取
- 优点:不经过shuffle过程,不需要将三台机器之间的数据进行交换,然后统一的分组
- 实现
package bigdata.hanjiaxiaozhi.cn.mr.join;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName MRDriver
* @Description TODO 这是MapReduce程序的Driver类的模板
* @Date 2020/5/30 10:34
* @Create By hanjiaxiaozhi
*/
public class MapJoinMr extends Configured implements Tool {
/**
* 用于将Job的代码封装
* @param args
* @return
* @throws Exception
*/
@Override
public int run(String[] args) throws Exception {
//todo:1-构建一个Job
Job job = Job.getInstance(this.getConf(),"model");//构建Job对象,调用父类的getconf获取属性的配置
job.setJarByClass(MapJoinMr.class);//指定可以运行的类型
//todo:2-配置这个Job
//input
// job.setInputFormatClass(TextInputFormat.class);//设置输入的类的类型,默认就是TextInputFormat
Path inputPath = new Path("datas/join/orders.txt");//将大的数据进行读取
//将订单作为输入
TextInputFormat.setInputPaths(job,inputPath);//为当前job设置输入的路径
//将商品表放入分布式缓存中
job.addCacheFile(new Path("datas/join/product.txt").toUri());
//map
job.setMapperClass(MRMapper.class);//设置Mapper的类,需要调用对应的map方法
job.setMapOutputKeyClass(Text.class);//设置Mapper输出的key类型
job.setMapOutputValueClass(Text.class);//设置Mapper输出的value类型
//shuffle
// job.setPartitionerClass(HashPartitioner.class);//自定义分区
// job.setGroupingComparatorClass(null);//自定义分组的方式
// job.setSortComparatorClass(null);//自定义排序的方式
//reduce
// job.setReducerClass(MRReducer.class);//设置Reduce的类,需要调用对应的reduce方法
// job.setOutputKeyClass(NullWritable.class);//设置Reduce输出的Key类型
// job.setOutputValueClass(NullWritable.class);//设置Reduce输出的Value类型
job.setNumReduceTasks(0);//设置ReduceTask的个数,默认为1
//output:输出目录默认不能提前存在
// job.setOutputFormatClass(TextOutputFormat.class);//设置输出的类,默认我诶TextOutputFormat
Path outputPath = new Path("datas/output/join/mapJoin");//用程序的第三个参数作为输出
//解决输出目录提前存在,不能运行的问题,提前将目前删掉
//构建一个HDFS的文件系统
FileSystem hdfs = FileSystem.get(this.getConf());
//判断输出目录是否存在,如果存在就删除
if(hdfs.exists(outputPath)){
hdfs.delete(outputPath,true);
}
TextOutputFormat.setOutputPath(job,outputPath);//为当前Job设置输出的路径
//todo:3-提交运行Job
return job.waitForCompletion(true) ? 0:-1;
}
/**
* 程序的入口,调用run方法
* @param args
*/
public static void main(String[] args) throws Exception {
//构建一个Configuration对象,用于管理这个程序所有配置,工作会定义很多自己的配置
Configuration conf = new Configuration();
//t通过Toolruner的run方法调用当前类的run方法
int status = ToolRunner.run(conf, new MapJoinMr(), args);
//退出程序
System.exit(status);
}
/**
* @ClassName MRMapper
* @Description TODO 这是MapReduce模板的Map类
* 输入的KV类型:由inputformat决定,默认是TextInputFormat
* 输出的KV类型:由map方法中谁作为key,谁作为Value决定
*/
public static class MRMapper extends Mapper<LongWritable, Text, Text,Text> {
//获取分布式缓存中的数据,存入Map集合,Key是商品id,Value是商品名称
Map<String,String> map = new HashMap<>();
/**
* Map类中有三个方法
* setup:在调用map之前会调用一次,类似于初始化的方法
* map:实现Map处理逻辑的方法
* cleanup:Map结束以后会调用的方法,相当于close方法
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void setup(Context context) throws IOException, InterruptedException {
//将缓存中的数据读取出来,封装到Map集合中
URI[] cacheFiles = context.getCacheFiles();
//打开这个缓存的文件
BufferedReader bufferedReader = new BufferedReader(new FileReader(cacheFiles[0].getPath()));
//将每一行的内容,封装到Map集合中
String line = null;
while(StringUtils.isNotBlank(line = bufferedReader.readLine())){
//分割将商品id和商品名称放入Map集合
String pid = line.split(",")[0];
String pname = line.split(",")[1];
map.put(pid,pname);
}
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//只有订单的数据,取出订单中的商品id
String pid = value.toString().split(",")[2];
//在Map集合中找到这个商品id对应的商品名称
String name = map.get(pid);
//输出订单信息和商品名称
context.write(value,new Text(name));
}
}
}