需求分析

如下两张输入表格

order

id pid amount
1001 01 1
1002 02 2
1003 03 3
1004 01 4
1005 02 5
1006 03 6

pd

pid pname
01 小米
02 华为
03 格力

将商品信息表中数据根据商品pid合并的订单数据表中。原文:sw-code

id pname amount
1001 小米 1
1004 小米 4
1002 华为 2
1005 华为 5
1003 格力 3
1006 格力 6

Reduce Join

创建一个TableBean对象,其包含两个文件的所有属性,方便在map阶段封装数据

public class TableBean implements Writable {

    private String id;
private String pid;
private Integer amount;
private String pname;
private String flag; public TableBean() {
} 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 Integer getAmount() {
return amount;
} public void setAmount(Integer 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;
}
}

在map阶段根据文件名来区分加载对象,setup方法一个文件只会执行一次,在该方法中获取文件名称,在map方法中根据文件名来执行不同的操作,值得注意的是属性不能为默认的NULL

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(Mapper<LongWritable, Text, Text, TableBean>.Context context) throws IOException, InterruptedException {
// 初始化
FileSplit inputSplit = (FileSplit) context.getInputSplit();
filename = inputSplit.getPath().getName();
} @Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, TableBean>.Context context) throws IOException, InterruptedException {
String line = value.toString();
//判断是哪个文件
if (filename.contains("order")) {
String[] split = line.split("\t");
// 封装k v
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");
// 封装k v
outK.set(split[0]);
outV.setId("");
outV.setPid(split[0]);
outV.setAmount(0);
outV.setPname(split[1]);
outV.setFlag("pd");
}
//写出
context.write(outK, outV);
}
}

由于使用pidkey,两个表中相同的pid会进入同一个reduce,再根据flag判断是哪个表中的数据,如果是order将其保存到数组中,如果是pd则获取其pname,循环order数组赋值。值得注意的是,由于values并非Java中默认的迭代器,如果只是add(value)赋值的是地址,无法达到预期要求。

public class TableReducer extends Reducer<Text, TableBean, TableBean, NullWritable> {

    @Override
protected void reduce(Text key, Iterable<TableBean> values, Reducer<Text, TableBean, TableBean, NullWritable>.Context context) throws IOException, InterruptedException {
ArrayList<TableBean> orderBeans = new ArrayList<>();
TableBean pBean = new TableBean();
for (TableBean value : values) {
if ("order".equals(value.getFlag())) {
TableBean tempTableBean = new TableBean();
try {
BeanUtils.copyProperties(tempTableBean, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
orderBeans.add(tempTableBean);
} else {
try {
BeanUtils.copyProperties(pBean, value);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
// 遍历orderBeans
for (TableBean orderBean : orderBeans) {
orderBean.setPname(pBean.getPname());
context.write(orderBean, NullWritable.get());
} }
}

总结:如果数据量非常大,所有的压力都会来到reduce阶段,这样会导致数据倾斜。为了防止发生,可以将Join操作放到map阶段,因为map阶段处理的数据都是块大小128M

Map Join

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

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

采用DistributedCache的方法:

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

(2)在Driver驱动类中加载缓存

// 缓存普通文件到Task运行节点
job.addCacheFile(new URI("file:///e:/cache/pd.txt"));
// 如果是集群运行,需要设置HDFS路径
job.addCacheFile(new URI("hdfs://hadoop102:8020/cache/pd.txt"));

实操案例

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

    private HashMap<String, String> pdMap = new HashMap<>();
private Text outK = new Text(); @Override
protected void setup(Mapper<LongWritable, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException {
// 获取缓存文件,并把文件内容封装到集合中 pd.txt
URI[] cacheFiles = context.getCacheFiles();
URI cacheFile = cacheFiles[0];
FileSystem fs = FileSystem.get(context.getConfiguration());
FSDataInputStream fis = fs.open(new Path(cacheFile)); // 从流中读取数据
BufferedReader reader = new BufferedReader(new InputStreamReader(fis, "UTF-8"));
String line;
while (StringUtils.isNotEmpty(line=reader.readLine())) {
// 切割
String[] fields = line.split("\t");
pdMap.put(fields[0], fields[1]);
}
IOUtils.closeStream(reader);
} @Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException { // 处理 order.txt
String line = value.toString();
String[] split = line.split("\t");
String pName = pdMap.get(split[1]); outK.set(split[0] + "\t" + pName + "\t" + split[2]);
context.write(outK, NullWritable.get());
}
}
Driver
public static void main(String[] args) throws IOException, URISyntaxException, InterruptedException, ClassNotFoundException {
Job job = Job.getInstance(new Configuration());
job.setMapperClass(MapJoinMapper.class);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(NullWritable.class); job.setOutputKeyClass(Text.class);
job.setOutputValueClass(NullWritable.class); job.addCacheFile(new URI("file:///D:/hadoop/input/mapjoincache/pd.txt"));
// 不需要reduce阶段
job.setNumReduceTasks(0); FileInputFormat.setInputPaths(job, new Path("D:\\hadoop\\input\\mapjoin"));
FileOutputFormat.setOutputPath(job, new Path("D:\\hadoop\\output\\mapjoin")); boolean b = job.waitForCompletion(true);
System.exit(b?0:1);
}
}

下篇文章:

相关文章:

大数据之Hadoop集群中Yarn常用命令

大数据之Hadoop集群的HDFS压力测试

大数据之Hadoop集群中MapReduce的Join操作的更多相关文章

  1. 大数据测试之hadoop集群配置和测试

    大数据测试之hadoop集群配置和测试   一.准备(所有节点都需要做):系统:Ubuntu12.04java版本:JDK1.7SSH(ubuntu自带)三台在同一ip段的机器,设置为静态IP机器分配 ...

  2. 大数据学习——HADOOP集群搭建

    4.1 HADOOP集群搭建 4.1.1集群简介 HADOOP集群具体来说包含两个集群:HDFS集群和YARN集群,两者逻辑上分离,但物理上常在一起 HDFS集群: 负责海量数据的存储,集群中的角色主 ...

  3. 大数据平台Hadoop集群搭建

    一.概念 Hadoop是由java语言编写的,在分布式服务器集群上存储海量数据并运行分布式分析应用的开源框架,其核心部件是HDFS与MapReduce.HDFS是一个分布式文件系统,类似mogilef ...

  4. Java+大数据开发——Hadoop集群环境搭建(一)

    1集群简介 HADOOP集群具体来说包含两个集群:HDFS集群和YARN集群,两者逻辑上分离,但物理上常在一起 HDFS集群: 负责海量数据的存储,集群中的角色主要有 NameNode / DataN ...

  5. Java+大数据开发——Hadoop集群环境搭建(二)

    1. MAPREDUCE使用 mapreduce是hadoop中的分布式运算编程框架,只要按照其编程规范,只需要编写少量的业务逻辑代码即可实现一个强大的海量数据并发处理程序 2. Demo开发--wo ...

  6. 大数据之hadoop集群安全模式

    集群安全模式1.概述(1)NameNode启动 NameNode启动时,首先将镜像文件(Fsimage)载入内存,并执行编辑日志(Edits)中的各项操作.-旦在内存中成功建立文件系统元数据的影像,则 ...

  7. 大数据学习——hadoop集群搭建2.X

    1.准备Linux环境 1.0先将虚拟机的网络模式选为NAT 1.1修改主机名 vi /etc/sysconfig/network NETWORKING=yes HOSTNAME=itcast ### ...

  8. CDH构建大数据平台-配置集群的Kerberos认证安全

     CDH构建大数据平台-配置集群的Kerberos认证安全 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 当平台用户使用量少的时候我们可能不会在一集群安全功能的缺失,因为用户少,团 ...

  9. 朝花夕拾之--大数据平台CDH集群离线搭建

    body { border: 1px solid #ddd; outline: 1300px solid #fff; margin: 16px auto; } body .markdown-body ...

  10. Hadoop集群中添加硬盘

    Hadoop工作节点扩展硬盘空间 接到老板任务,Hadoop集群中硬盘空间不够用,要求加一台机器到Hadoop集群,并且每台机器在原有基础上加一块2T硬盘,老板给力啊,哈哈. 这些我把完成这项任务的步 ...

随机推荐

  1. 异步、多线程、Java爬取某网站图片

    一.网页图片爬取类 package com.yhyl.utils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import or ...

  2. 抓包整理————ip 协议二[十三]

    前言 介绍一下什么是nat协议和napt协议,和简单带一下LVS. 正文 什么是nat(Network Address Translation) 协议呢? 比如现在你家分配了一个ip,但是你家有10个 ...

  3. JavaSE开发基础--包机制&JavaDoc&Scanner&循环结构&方法&数组

    包机制 如果文件在包中需要 在文件首行添加 package 地址 package pkg1.pkg2.pkg3 import package1 JavaDoc /** * @author作者名 * @ ...

  4. mmcls/mmdet模型部署至 TorchServe

    mmcls/mmdet模型部署至 TorchServe 官方教程:模型部署至 TorchServe - MMClassification 0.23.2 文档 接口说明: serve/inference ...

  5. nethttp和gin 路由

    net/http 路由注册 func test1() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Requ ...

  6. BURP保存多个监听器配置

    "感谢您阅读本篇博客!如果您觉得本文对您有所帮助或启发,请不吝点赞和分享给更多的朋友.您的支持是我持续创作的动力,也欢迎留言交流,让我们一起探讨技术,共同成长!谢谢!" 前言 在进 ...

  7. Spark如何对源端数据做切分?

    简介: 典型的Spark作业读取位于OSS的Parquet外表时,源端的并发度(task/partition)如何确定?特别是在做TPCH测试时有一些疑问,如源端扫描文件的并发度是如何确定的?是否一个 ...

  8. 阿里云视觉智能开放平台正式上线,阿里集团核心视觉AI能力对外开放

    1月底,阿里云正式推出以计算机视觉AI能力为核心的视觉智能开放平台(vision.aliyun.com),平台目前已上线8大类目,超过50多种视觉AI能力,面向人脸识别,文字识别,商品理解,内容安全, ...

  9. What's new in dubbo-go v1.5.6

    简介: dubbogo 社区近期发布了 dubbogo v1.5.6.该版本和 dubbo 2.7.8 对齐,提供了命令行工具,并提供了多种加载配置的方式. 作者 | 铁城  dubbo-go 社区 ...

  10. 来电科技:基于 Flink + Hologres 的实时数仓演进之路

    简介: 本文将会讲述共享充电宝开创企业来电科技如何基于 Flink + Hologres 构建统一数据服务加速的实时数仓 作者:陈健新,来电科技数据仓库开发工程师,目前专注于负责来电科技大数据平台离线 ...