hadoop-MapReduce-Join

 ——尚硅谷课程笔记


Reduce Join

    Map端的主要工作:为来自不同表文件的key/value打标签以区别不同来源的记录。然后用连接字段作为key,其余部分和新加的标志作为value,最后进行输出。

    Reduce端的主要工作:在Reduce端以连接字段作为key的分组已经完成,我们只需要在每一个分组当中将那些来源于不同文件的记录(Map阶段已经打标志)分开,最后进行合并就ok了。

Reduce Join案例

需求

订单数据表,order.txt

id

pid

amount

1001

01

1

1002

02

2

1003

03

3

1004

01

4

1005

02

5

1006

03

6

 商品信息表,pd.txt

pid

pname

01

小米

02

华为

03

格力

将商品信息表中数据根据商品pid合并到订单数据表中。

最终数据形式

id

pname

amount

1001

小米

1

1004

小米

4

1002

华为

2

1005

华为

5

1003

格力

3

1006

格力

6

需求分析

通过将关联条件作为Map输出的key,将两表满足Join条件的数据并携带数据所来源的文件信息,发往同一个ReduceTask,在Reduce中进行数据的串联。

代码实现

1)创建商品和订合并后的Bean类

package com.liun.mr.reducejoin;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.io.Writable;

public class TableBean implements Writable{

	private String order_id;
	private String p_id;
	private int amount;
	private String pname;
	private String flag;
	
	public TableBean() {
		super();
	}

	public TableBean(String order_id, String p_id, int amount, String pname, String flag) {
		super();
		this.order_id = order_id;
		this.p_id = p_id;
		this.amount = amount;
		this.pname = pname;
		this.flag = flag;
	}

	@Override
	public void write(DataOutput out) throws IOException {
		
		// 序列化
		out.writeUTF(order_id);
		out.writeUTF(p_id);
		out.writeInt(amount);;
		out.writeUTF(pname);
		out.writeUTF(flag);
	}

	@Override
	public void readFields(DataInput in) throws IOException {
		
		// 反序列化
		order_id = in.readUTF();
		p_id = in.readUTF();
		amount = in.readInt();
		pname = in.readUTF();
		flag = in.readUTF();
	}

	public String getOrder_id() {
		return order_id;
	}

	public void setOrder_id(String order_id) {
		this.order_id = order_id;
	}

	public String getP_id() {
		return p_id;
	}

	public void setP_id(String p_id) {
		this.p_id = p_id;
	}

	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 String toString() {
		return  order_id + "\t" + amount + "\t" + pname;
	}
}

2)编写TableMapper类

package com.liun.mr.reducejoin;

import java.io.IOException;

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;

public class TableMapper extends Mapper<LongWritable, Text, Text, TableBean> {

	String name;
	TableBean tableBean = new TableBean();
	Text k = new Text();

	@Override
	protected void setup(Context context) throws IOException, InterruptedException {

		// 获取文件名称
		FileSplit inputSplit = (FileSplit) context.getInputSplit();

		name = inputSplit.getPath().getName();
	}

	@Override
	protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

		// 获取一行
		String line = value.toString();

		if (name.startsWith("order")) {// 订单表

			String[] fields = line.split("\t");

			tableBean.setOrder_id(fields[0]);
			tableBean.setP_id(fields[1]);
			tableBean.setAmount(Integer.parseInt(fields[2]));
			tableBean.setPname("");
			tableBean.setFlag("order");

			k.set(fields[1]);

		} else {// 产品表

			String[] fields = line.split("\t");

			tableBean.setOrder_id("");
			tableBean.setP_id(fields[0]);
			tableBean.setAmount(0);
			tableBean.setPname(fields[1]);
			tableBean.setFlag("pd");

			k.set(fields[0]);
		}

		// 写出
		context.write(k, tableBean);
	}
}

3)编写TableReducer类

package com.liun.mr.reducejoin;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class TableReducer<E> 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 tableBean : values) {

			if ("order".equals(tableBean.getFlag())) {// 订单表

				TableBean tmpBean = new TableBean();

				try {
					// 拷贝传递过来的每条订单数据到集合中
					BeanUtils.copyProperties(tmpBean, tableBean);

					orderBeans.add(tmpBean);

				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			} else {// 产品表
				try {
					// 拷贝传递过来的产品表到内存中
					BeanUtils.copyProperties(pdBean, tableBean);

				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
		}

		for (TableBean tableBean : orderBeans) {
			tableBean.setPname(pdBean.getPname());

			// 写出
			context.write(tableBean, NullWritable.get());
		}
	}
}

4)编写TableDriver类

package com.liun.mr.reducejoin;

import java.io.IOException;

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;

public class TableDriver {

	public static void main(String[] args) throws Exception, IOException {

		// 1 获取配置信息,或者job对象实例
		Configuration configuration = new Configuration();
		Job job = Job.getInstance(configuration);

		// 2 指定本程序的jar包所在的本地路径
		job.setJarByClass(TableDriver.class);

		// 3 指定本业务job要使用的Mapper/Reducer业务类
		job.setMapperClass(TableMapper.class);
		job.setReducerClass(TableReducer.class);

		// 4 指定Mapper输出数据的kv类型
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(TableBean.class);

		// 5 指定最终输出的数据的kv类型
		job.setOutputKeyClass(TableBean.class);
		job.setOutputValueClass(NullWritable.class);

		// 6 指定job的输入原始文件所在目录
		FileInputFormat.setInputPaths(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));

		// 7 将job中配置的相关参数,以及job所用的java类所在的jar包, 提交给yarn去运行
		boolean result = job.waitForCompletion(true);
		System.exit(result ? 0 : 1);

	}
}

缺点:这种方式中,合并的操作是在Reduce阶段完成,Reduce端的处理压力太大,Map节点的运算负载则很低,资源利用率不高,且在Reduce阶段极易产生数据倾斜。

解决方案:Map端实现数据合并

Map Join

1.使用场景

Map Join适用于一张表十分小、一张表很大的场景。

2.优点

思考:在Reduce端处理过多的表,非常容易产生数据倾斜。怎么办?

在Map端缓存多张表,提前处理业务逻辑,这样增加Map端业务,减少Reduce端数据的压力,尽可能的减少数据倾斜。

3.具体办法:采用DistributedCache

       (1)在Mapper的setup阶段,将文件读取到缓存集合中。

       (2)在驱动函数中加载缓存。

// 缓存普通文件到Task运行节点。

job.addCacheFile(new URI("file://e:/cache/pd.txt"));

 Map Join案例

需求与Reduce Join相同

实现代码

(1)先在驱动模块中添加缓存文件

package com.liun.mr.mapjoin;

import java.io.IOException;
import java.net.URI;

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;

public class DistributedCacheDriver {

	public static void main(String[] args) throws Exception, IOException {
		
		// 1 获取job信息
		Configuration configuration = new Configuration();
		Job job = Job.getInstance(configuration);

		// 2 设置加载jar包路径
		job.setJarByClass(DistributedCacheDriver.class);

		// 3 关联map
		job.setMapperClass(DistributedCacheMapper.class);

		// 4 设置最终输出数据类型
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(NullWritable.class);

		// 5 设置输入输出路径
		FileInputFormat.setInputPaths(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));

		// 6 加载缓存数据
		job.addCacheFile(new URI("file:///e:/input/inputcache/pd.txt"));

		// 7 Map端Join的逻辑不需要Reduce阶段,设置reduceTask数量为0
		job.setNumReduceTasks(0);

		// 8 提交
		boolean result = job.waitForCompletion(true);
		System.exit(result ? 0 : 1);

	}
}

(2)读取缓存的文件数据

package com.liun.mr.mapjoin;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;

import org.apache.commons.lang.StringUtils;
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;

public class DistributedCacheMapper extends Mapper<LongWritable, Text, Text, NullWritable> {

	HashMap<String, String> pdMap = new HashMap<>();
	Text k = new Text();

	@Override
	protected void setup(Mapper<LongWritable, Text, Text, NullWritable>.Context context)
			throws IOException, InterruptedException {
		// 缓存小表
		URI[] cacheFiles = context.getCacheFiles();
		String path = cacheFiles[0].getPath().toString();

		BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF-8"));

		String line;
		while (StringUtils.isNotEmpty(line = reader.readLine())) {

			// 切割
			String[] fileds = line.split("\t");

			pdMap.put(fileds[0], fileds[1]);
		}

		// 关闭资源
		IOUtils.closeStream(reader);
	}

	@Override
	protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {

		// 获取一行
		String line = value.toString();

		// 切割
		String[] fileds = line.split("\t");

		// 获取pid
		String pid = fileds[1];

		// 获取pname
		String pname = pdMap.get(pid);
		System.out.println(pdMap.get("01"));
		// 拼接
		line = line + "\t" + pname;

		k.set(line);

		// 写出
		context.write(k, NullWritable.get());
	}
}

在第一次出现bug,运行结果出现null,都是pid为01的pname

1001    01    1    null
1002    02    2    华为
1003    03    3    格力
1004    01    4    null
1005    02    5    华为
1006    03    6    格力

打断点debug发现读取的第一行数据有问题

检查源文件,表面看没问题,用插件查看发现

查资料发现是Windows自带的记事本,另存为utf-8会自带BOM

解决方法一,修改源数据

使用notepad++将数据改为UTF-8无BOM格式编码

解决方法二,BOMInputStream

使用BOMInputStream自动去除UTF-8中的BOM

将reader修改为

BufferedReader reader = new BufferedReader(new InputStreamReader(new BOMInputStream(new FileInputStream(path)) , "UTF-8"));

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为在CentOS 7上配置Hadoop,您可以按照以下步骤进行操作: 1. 下载和安装Hadoop:首先,您需要从Hadoop官方网站下载适用于CentOS 7的Hadoop版本,并将其解压缩到所需的目录中。 2. 配置环境变量:进入Hadoop的安装目录,编辑`~/.bashrc`文件,并在其中添加以下内容: ```shell export HADOOP_HOME=/usr/local/hadoop export PATH=$PATH:$HADOOP_HOME/bin ``` 3. 修改Hadoop配置文件:进入`/usr/local/hadoop/etc/hadoop/`目录,使用文本编辑器打开`core-site.xml`文件。在其中添加以下内容: ```xml <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://localhost:9000</value> </property> </configuration> ``` 4. 启动Hadoop:使用以下命令启动Hadoop集群: ```shell $ start-dfs.sh # 启动Hadoop分布式文件系统(HDFS) $ start-yarn.sh # 启动YARN资源管理器 ``` 5. 验证Hadoop是否已成功安装:使用以下命令检查Hadoop版本信息: ```shell $ hadoop version ``` 6. 运行Hadoop示例:Hadoop附带了一些示例程序,您可以运行这些示例程序来熟悉Hadoop的运行方式。例如,您可以运行WordCount示例程序: ```shell $ hadoop jar /usr/local/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount input output ``` 请注意,以上步骤仅为概述,具体操作可能因您的环境和需求而有所不同。在配置Hadoop之前,请确保您已经安装了Java并正确配置了Java环境变量。 : Hadoop 解压后即可使用,输入以下命令检查 Hadoop 是否可用,成功则会显示 Hadoop 版本信息: ``` cd /usr/local/hadoop # 进入/usr/local/hadoop目录 ./bin/hadoop version # 查看hadoop版本信息 ``` : Hadoop 默认模式为非分布式模式(本地模式),无需进行其他配置即可运行。即 Hadoop 安装成功后即可使用。我们可以执行一些例子来感受一下 Hadoop 的运行。Hadoop 附带了丰富的例子,运行以下命令可以看到所有例子,包括 wordcount、terasort、join、grep 等: ``` ./bin/hadoop jar ./share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar ``` : 执行以下命令修改 core-site.xml 文件: ``` cd /usr/local/hadoop/etc/hadoop/ # 进入/usr/local/hadoop/etc/hadoop/目录 sudo vi core-site.xml # 编辑 core-site.xml 文件 ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值