目录
Reduce Join
Map端主要工作
为来自不同表或文件的key/value对,打标签以区别不同来源的记录。
然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出。
Reduce端主要工作
在Reduce端以连接字段作为key的分组已经完成,只需要在每一个分组当中将那些来源于不同文件的记录(在Map阶段已经打标志)分开,最后进行合并就ok了。
Reduce Join案例实操
需求
分析:MapTask任务
- 首先需要确定,MapTask最终会输出KV对,其KV对作为Reduce阶段排序的参考,所以Map要做的事情就是正确分出Key和Value。
- 又知两张表唯一相同的字段为pid,所以考虑将key设为pid
- Value考虑Bean序列化封装,其类包含两张表里其它所有字段,再分别赋值即可。
分析:ReduceTask任务
- 首先colltction会根据key进行一个排序分组,Reduce再对分好组的kv对进行处理,处理成期望输出的样式即可。
TableBean序列化代码
package com.mapreduce.ReduceJoin;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class TableBean implements Writable {
//id pid amount pid pname
private String id; //订单id
private String pid; //商品id
private int amount; //商品数量
private String pname; //商品名称
private String flag;//标记是什么表 order和pd
//空参构造
public TableBean() {
}
//get set方法
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public String getFlag() {
return flag;
}
public void setFlag(String flag) {
this.flag = flag;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(id);
dataOutput.writeUTF(pid);
dataOutput.writeInt(amount);
dataOutput.writeUTF(pname);
dataOutput.writeUTF(flag);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.id = dataInput.readUTF();
this.pid = dataInput.readUTF();
this.amount = dataInput.readInt();
this.pname = dataInput.readUTF();
this.flag = dataInput.readUTF();
}
@Override
public String toString() {
return id + "\t" + pname + "\t" + amount;
}
}
Mapper代码
重写setup()初始化方法
因为文件切片是一个文件切的,所以在setup()里获取,可以只运行一次就可以获取文件名称,如果放到map()方法里,则是每处理一行就获取一次,很麻烦。
要重写setup初始化方法,因为需要从两张表里读取获取文件名称,进行区分。
总代码
package com.mapreduce.ReduceJoin;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileSplit;
import java.io.IOException;
public class TableMapper extends Mapper<LongWritable, Text,Text,TableBean> {
private String filename;
private Text outK = new Text();
private TableBean outV = new TableBean();
//初始化方法 因为会读取两个文件 所以需要
@Override
protected void setup(Context context) throws IOException, InterruptedException {
FileSplit split = (FileSplit) context.getInputSplit();
filename = split.getPath().getName();
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
//判断哪个文件
if(filename.contains("order")){
String[] split = line.split("\t");
//封装对应的KV
outK.set(split[1]);
outV.setId(split[0]);
outV.setPid(split[1]);
outV.setAmount(Integer.parseInt(split[2]));
outV.setPname(""); //不存在的给默认值
outV.setFlag("order");
}else {
String[] split = line.split("\t");
//封装outK
outK.set(split[0]);
//封装outV
//默认值
outV.setId("");
outV.setPid(split[0]);
outV.setAmount(0);
outV.setPname(split[1]);
outV.setFlag("pd");
}
//写出KV
context.write(outK,outV);
}
}
Reducer代码
package com.mapreduce.ReduceJoin;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import org.codehaus.jackson.map.util.BeanUtil;
import javax.xml.soap.Text;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
//打印时直接调用TableBean里的toString()方法 故返回NullWritable
public class TableReducer extends Reducer<Text,TableBean,TableBean, NullWritable> {
@Override
protected void reduce(Text key, Iterable<TableBean> values, Context context) throws IOException, InterruptedException {
//创建集合
ArrayList<TableBean> orderBeans = new ArrayList<TableBean>;
//因为pd表里的只有一行值 所以让TableBean存储即可
TableBean pdBean = new TableBean();
//循环遍历
for (TableBean value : values) {
if ("order".equals(value.getFlag())) {
//说明这个数据在订单表里
//创建临时对象接收
TableBean temp = new TableBean();//用value克隆
try {
BeanUtils.copyProperties(temp, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
orderBeans.add(temp);
} else { //说明这个数据在商品表里
try {
BeanUtils.copyProperties(pdBean, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
//循环遍历orderBeans 赋值pdname
/*
01 1001 1 order
01 1004 4 order
*/
//变为
/*
01 1001 1 order 小米
01 1004 4 order 小米
*/
for (TableBean orderBean : orderBeans) {
orderBean.setPname(pdBean.getPname());
context.write(orderBean, NullWritable.get());
}
}
}
Reduce join总结
这种方式中,合并的操作是在Reduce阶段完成,Reduce端的处理压力太大,Map节点的运算负载则很低,资源利用率不高,且在Reduce阶段极易产生数据倾斜。
Map join
使用场景
Map Join适用于一张表十分小、一张表很大的场景。
优点
在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能的减少数据倾斜。
具体办法:DistributedCache
- 在Mapper的setup阶段,将文件读取到缓存集合中。
- 在Driver驱动类中加载缓存。
//缓存普通文件到Task运行节点。
job.addCacheFile(new URI("file:///e:/cache/pd.txt"));
//如果是集群运行,需要设置HDFS路径
job.addCacheFile(new URI("hdfs://hadoop102:8020/cache/pd.txt"));
Map join案例实操
其中需求和上述一样,差别在于要求在Mapper里面实现Join
分析:Driver端
加载缓存数据,同时因为Map端已经完成了Join,不再需要Reduce阶段了,所以设置ReduceTask数据量为0
//加载缓存数据
job.addCacheFile(new URI("file://e://cache/pd.txt"));
//Map端join的逻辑不需要Reduce阶段,设置ReduceTask数量为0
job.setNumreduceTasks(0);
分析:MapTask任务
在setup()初始化方法里,获取缓存文件,并且循环读取缓存文件中一行,切割完后缓存数据到集合。
在map()方法后,根据pid获取出商品名,进行拼接后给出。
Driver代码
package com.mapreduce.MapJoin;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
public class MapJoinDriver {
public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InterruptedException {
// 1 获取job信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 2 设置加载jar包路径
job.setJarByClass(MapJoinDriver.class);
// 3 关联mapper
job.setMapperClass(MapJoinMapper.class);
// 4 设置Map输出KV类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class);
// 5 设置最终输出KV类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class);
// 加载缓存数据
job.addCacheFile(new URI("file:///D:/input/tablecache/pd.txt"));
// Map端Join的逻辑不需要Reduce阶段,设置reduceTask数量为0
job.setNumReduceTasks(0);
// 6 设置输入输出路径
FileInputFormat.setInputPaths(job, new Path("D:\\input"));
FileOutputFormat.setOutputPath(job, new Path("D:\\output"));
// 7 提交
boolean b = job.waitForCompletion(true);
System.exit(b ? 0 : 1);
}
}
Mapper代码
package com.mapreduce.MapJoin;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
public class MapJoinMapper extends Mapper<LongWritable, Text,Text, NullWritable> {
//存储对应的HashMap
private Map<String, String> pdMap = new HashMap<>();
private Text text = new Text();
@Override
protected void setup(Context context) throws IOException, InterruptedException {
//获取缓存的文件 并把文件内容封装到集合 封装pd.txt
//pd.txt 内容为 01 小米 02 华为 03 格力
URI[] cacheFiles = context.getCacheFiles();
Path path = new Path(cacheFiles[0]);
//获取文件系统对象,并开流
FileSystem fs = FileSystem.get(context.getConfiguration());
FSDataInputStream fis = fs.open(path);
//从流中读取数据 通过包装流转换为reader,方便按行读取
BufferedReader reader = new BufferedReader(new InputStreamReader(fis, "UTF-8"));
//逐行读取,按行处理
String line;
while (StringUtils.isNotEmpty(line = reader.readLine())) {
//切割一行
//01 小米
String[] split = line.split("\t");
pdMap.put(split[0], split[1]);
}
//关流
IOUtils.closeStream(reader);
}
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//读取大表数据
//1001 01 1
String[] fields = value.toString().split("\t");
//通过大表每行数据的pid,去pdMap里面取出pname
String pname = pdMap.get(fields[1]);
//将大表每行数据的pid替换为pname
text.set(fields[0] + "\t" + pname + "\t" + fields[2]);
//写出
context.write(text,NullWritable.get());
}
}