1.使用场景
适用于一张表十分小、一张表很大的场景。
2.优点
思考:在Reduce端处理过多的表,非常容易产生数据倾斜。怎么办?
在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能的减少数据倾斜。
3.具体步骤
- 在Mapper的setup阶段,将文件读取到缓存集合中。
- 在驱动函数中加载缓存。
4.需求
表4-4 订单数据表t_order
id | pid | amount |
---|---|---|
1001 | 01 | 1 |
1002 | 02 | 2 |
1003 | 03 | 3 |
1004 | 01 | 4 |
1005 | 02 | 5 |
1006 | 03 | 6 |
表4-5 商品信息表t_product
pid | pname |
---|---|
01 | 小米 |
02 | 华为 |
03 | 格力 |
表4-6 最终数据形式
id | pname | amount |
---|---|---|
1001 | 小米 | 1 |
1004 | 小米 | 4 |
1002 | 华为 | 2 |
1005 | 华为 | 5 |
1003 | 格力 | 3 |
1006 | 格力 | 6 |
5.代码实现
1.bean对象
package com.zj.practice.mapreduce09.order_map_join;
import org.apache.hadoop.io.Writable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class order_MapJoin_Bean implements Writable {
/**
* 订单id
*/
private String orderId;
/**
* 产品id
*/
private String productId;
/**
* 商品名称
*/
private String productName;
/**
* 商品数量
*/
private int amount;
public order_MapJoin_Bean() {
super();
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount = amount;
}
@Override
public String toString() {
return orderId + "\t" + productName + "\t" + amount;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(orderId);
dataOutput.writeUTF(productId);
dataOutput.writeUTF(productName);
dataOutput.writeInt(amount);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
orderId = dataInput.readUTF();
productId = dataInput.readUTF();
productName = dataInput.readUTF();
amount = dataInput.readInt();
}
}
2.mapper端
package com.zj.practice.mapreduce09.order_map_join;
import org.apache.commons.lang.StringUtils;
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.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
/**
* 思考:在Reduce端处理过多的表,非常容易产生数据倾斜。怎么办?
* 在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能的减少数据倾斜。
* 具体办法:采用DistributedCache
* (1)在Mapper的setup阶段,将文件读取到缓存集合中。
* (2)在驱动函数中加载缓存。
* 缓存普通文件到Task运行节点。
* job.addCacheFile(new URI("file:///e:/cache/pd.txt"));
*/
public class order_MapJoin_Mapper extends Mapper<LongWritable, Text, order_MapJoin_Bean, NullWritable> {
// 存放小表数据 --> pdid,pdname
private HashMap<String, String> hashMap = new HashMap<>();
// 输出的Bean对象
private order_MapJoin_Bean orderBean = new order_MapJoin_Bean();
// 1.获取缓存的小表数据
@Override
protected void setup(Context context) throws IOException, InterruptedException {
System.out.println("maptask开始执行");
// 获取缓存的uri数组
URI[] cacheFiles = context.getCacheFiles();
// 这里只有一个小表,所以放在0号位置
// 获取小表的路径
String smallPath = cacheFiles[0].getPath().toString();
// 读取小表数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(smallPath)));
// 一次读取一行数据
String line;
while (StringUtils.isNotEmpty(line = bufferedReader.readLine())) {
// 分隔字符串
String[] split = line.split("\t");
// 将0号元素-->pid作为key,1号元素-->pname作为value存储在hashmap中
hashMap.put(split[0], split[1]);
}
}
// 2.读取大表数据
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 获取大表数据
String line = value.toString();
// 分隔字符串
String[] split = line.split("\t");
// 为bean对象赋值
orderBean.setOrderId(split[0]);
orderBean.setProductId(split[1]);
orderBean.setAmount(Integer.parseInt(split[2]));
// 3.合并大小表数据
orderBean.setProductName(hashMap.get(orderBean.getProductId()));
// 写出
context.write(orderBean, NullWritable.get());
}
@Override
protected void cleanup(Context context) throws IOException, InterruptedException {
System.out.println("maptask结束");
}
}
3.driver端
package com.zj.practice.mapreduce09.order_map_join;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
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 order_MapJoin_Driver {
public static void main(String[] args) throws IOException, URISyntaxException, ClassNotFoundException, InterruptedException {
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
job.setJarByClass(order_MapJoin_Driver.class);
job.setMapperClass(order_MapJoin_Mapper.class);
job.setMapOutputKeyClass(order_MapJoin_Bean.class);
job.setMapOutputValueClass(NullWritable.class);
// 加载小表文件到缓存
job.addCacheFile(URI.create("file:///E:/BigData/MapReduce01/src/test/input/mapJoin/pd.txt"));
//设置reduceTask的数量为0
job.setNumReduceTasks(0);
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
boolean res = job.waitForCompletion(true);
System.exit(res ? 0 : 1);
}
}