大数据学习----------MapReduce----------(七)Join,压缩

本文详细介绍了Hadoop中Reduce端Join的具体实现方法,并解决了迭代器重用导致的数据问题。通过案例展示了如何整合多张表的数据,同时提出了Map端Join方案以解决数据倾斜等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Hadoop reduce阶段迭代器重用问题

protected void reduce(Text key, Iterable<TableBean> values, Context context) throws IOException, InterruptedException {
        ArrayList<TableBean> orederBeans = new ArrayList<>();
        TableBean pbBean = new TableBean();
        for (TableBean value : values) {
            orederBeans.add(value)
        }
    }

看似是普通的迭代数据添加到list列表中,但是查出来的数据是一样的很多条数据,这是为什么呢?
这里的原因是hadoop默认重写的Iterable迭代方法,每次迭代都是用的同一个对象,然后更换下对象得地址,所以你每天add得都是同一个对象,最终都指向一块地址。

Join 应用
分为Reduce端jion和Map端jion
1、Reduce端jion
Map 端的主要工作:为来自不同表或文件的 key/value 对,打标签以区别不同来源的记录。然后用连接字段作为 key,其余部分和新加的标志作为 value,最后进行输出。
Reduce 端的主要工作:在 Reduce 端以连接字段作为 key 的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(在 Map 阶段已经打标志)分开,最后进行合并就 ok 了。
案例:
在这里插入图片描述
主要实现方案是把order表的字段和pd表的字段合并起来生成tableBean

Mapper阶段根据不同文件名称存储到tableBean对象中

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 {
 //获取对应文件名称
	 InputSplit split = context.getInputSplit();
	 FileSplit fileSplit = (FileSplit) split;
	 filename = fileSplit.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");
	 //封装 outK
	 outK.set(split[1]);
	 //封装 outV
	 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);
 	} 
 }

这里在setup方法中提前可以设置maptask处理的文件,应为两个文件,分配了2个mapTask处理,所以提前区分出来并在map方法中做出判断。

TableReducer
这里注意Hadoop reduce阶段迭代器重用问题
根据map阶段的数据的数据把两张表整合在一起

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 pdBean = new TableBean();
	 for (TableBean value : values) {
		 //判断数据来自哪个表
		 if("order".equals(value.getFlag())){ //订单表
			 //创建一个临时 TableBean 对象接收 value
			 TableBean tmpOrderBean = new TableBean();
			 try {
			 	BeanUtils.copyProperties(tmpOrderBean,value);
			 } catch (IllegalAccessException e) {
			 	e.printStackTrace();
			 } catch (InvocationTargetException e) {
			 	e.printStackTrace();
			 }
			 //将临时 TableBean 对象添加到集合 orderBeans
			 orderBeans.add(tmpOrderBean);
		 }else { //商品表
			 try {
				 	BeanUtils.copyProperties(pdBean,value);
				 } catch (IllegalAccessException e) {
				 	e.printStackTrace();
				 } catch (InvocationTargetException e) {
				 	e.printStackTrace();
				 }
			 }
	 	 }
	 //遍历集合 orderBeans,替换掉每个 orderBean 的 pid 为 pname,然后写出
	 for (TableBean orderBean : orderBeans) {
	 	orderBean.setPname(pdBean.getPname());
	 	//写出修改后的 orderBean 对象
	 	context.write(orderBean,NullWritable.get());
	 	}
	 } 
 }

注意有个问题,一次reduce方法获取一组key一样的数据,我们这边的key为两张表的主键pid,所以每次Iterable values必定有一个pd表的orderBean对象和多个order表的orderBean,在这里做整合。

问题?如果一个商品有几亿条订单,另外一个商品只有几个订单,必定会导致(如果开启多个reduce情况下)一个reduce很快处理完,另外个要处理好久,即数据倾斜,还有map阶段资源利用率不高,整体的运行时间加长。

解决方案,在Map阶段对数据进行合并,增加map阶段的利用率,有效减少reduce阶段数据倾斜。
主要方案在Driver增加缓存文件的读取,把pb表放到缓存文件中

// 加载缓存数据
 job.addCacheFile(new URI("file:///D:/input/tablecache/pd.txt"));
 // Map 端 Join 的逻辑不需要 Reduce 阶段,设置 reduceTask 数量为 0
 job.setNumReduceTasks(0);

然后再MapJoinMapper的setup方法(类似before方法)中把文件读取出来加入到Map集合中

//通过缓存文件得到小表数据 pd.txt
 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);

然后再map中对数据进行处理

//读取大表数据 
//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());
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值