携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
在上一个案例中我们使用的是reduce端的join实现了两个表之间的连接,但是reduce端join会使大量数据在传输过程中较多产生资源的消耗,同时也会造成数据倾斜。如果我们能将这些数据在map端进行处理,这样在数据传给reduce端的时候就会压力较小,并且此时reduce端不需要在进行特殊处理,所以我们就可以将reduceTask的个数设置为0。所以该案例中我们尝试将上一案例中的需求通过map端的join来实现一下,这次我们将在本地windows上运行代码。
map join的大致思路就是在mapper中初始化时将一张小表进行缓存,然后在map阶段用大表来对这个小表进行join,该案例中我们会将部门表进行缓存。在该案例中由于不需要reduce机型处理,所以我们不需要创建自定义Reducer;由于key只是用了其中一个字段(部门编号),所以也不需要使用多个属性定义Bean类。
- 准备数据
数据准备工作参考上一篇中的步骤。
新建project:
- 引入pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>wyh.test3</groupId>
<artifactId>mapjoin</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>3.1.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- 自定义Mapper
package wyh.test;
import org.apache.commons.lang3.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;
public class MapJoinMapper extends Mapper<LongWritable, Text, Text, NullWritable> {
private HashMap<String, String> deptMap = new HashMap<>();
//在初始化时缓存部门表
@Override
protected void setup(Context context) throws IOException, InterruptedException {
//获取缓存文件列表,由于我们在JobMain设置了将部门表添加至缓存,所以当前该缓存文件列表中只有一个元素
URI[] cacheFiles = context.getCacheFiles();
//获取文件系统
FileSystem fileSystem = FileSystem.get(context.getConfiguration());
//打开缓存文件,当前只有一个缓存文件,所以下标为0
FSDataInputStream stream = fileSystem.open(new Path(cacheFiles[0]));
//读数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(stream));
//声明读到的每一行数据
String line;
//遍历读取文件
while (StringUtils.isNotEmpty(line = bufferedReader.readLine())){
//拆分每一行的数据获取字段
String[] split = line.split(",");
//将表中部门编号作为key,部门名称作为value存放在Map类型集合中(注意Map集合是要在全局声明的,因为下面map()阶段需要用到)
deptMap.put(split[0], split[1]);
}
//关闭流
IOUtils.closeStream(bufferedReader);
}
//处理员工表,并与部门表join
@Override
protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException {
//获取员工表的行数据并拆分获取字段
String[] split = value.toString().split(",");
//从行数据中获取部门编号
String deptid = split[2];
//从上面的部门表Map集合中获取该条数据的部门编号对应的部门名称
String dept = deptMap.get(deptid);
//拼接要输出的key值
String out = split[0] + "," + split[1] + "," + dept;
//写出
context.write(new Text(out), NullWritable.get());
}
}
- 自定义主类
package wyh.test;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
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 org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
import java.net.URI;
public class MapJoinJobMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
Job job = Job.getInstance(super.getConf(), "testmapjoinJob");
//!!!!!!!!!! 集群必须要设置 !!!!!!!!
job.setJarByClass(MapJoinJobMain.class);
//配置job具体要执行的任务步骤
//指定要读取的文件的路径,这里写了目录,就会将该目录下的所有文件都读取到(这里只需要放employee.txt即可)
FileInputFormat.setInputPaths(job, new Path("D:\test_hdfs"));
//指定map处理逻辑类
job.setMapperClass(MapJoinMapper.class);
//指定map阶段输出的k2类型
job.setMapOutputKeyClass(Text.class);
//指定map阶段输出的v2类型
job.setMapOutputValueClass(NullWritable.class);
//设置reduce之后输出的k3类型
job.setOutputKeyClass(Text.class);
//设置reduce之后输出的v3类型
job.setOutputValueClass(NullWritable.class);
//将dept.txt加载至缓存(特别要注意的是使用map join时要缓存的表和其他表不可以放在同一目录下)
job.addCacheFile(new URI("file:///D:/test_mapjoin/dept.txt"));
//由于map端已经把预期的输出结果处理好了,不需要reduce端再处理,所以这里设置reduceTask个数为0
job.setNumReduceTasks(0);
//指定结果输出路径,该目录必须是不存在的目录(如已存在该目录,则会报错),它会自动帮我们创建
FileOutputFormat.setOutputPath(job, new Path("D:\testmapjoinouput"));
//返回执行状态
boolean status = job.waitForCompletion(true);
//使用三目运算,将布尔类型的返回值转换为整型返回值,其实这个地方的整型返回值就是返回给了下面main()中的runStatus
return status ? 0:1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
/**
* 参数一是一个Configuration对象,参数二是Tool的实现类对象,参数三是一个String类型的数组参数,可以直接使用main()中的参数args.
* 返回值是一个整型的值,这个值代表了当前这个任务执行的状态.
* 调用ToolRunner的run方法启动job任务.
*/
int runStatus = ToolRunner.run(configuration, new MapJoinJobMain(), args);
/**
* 任务执行完成后退出,根据上面状态值进行退出,如果任务执行是成功的,那么就是成功退出,如果任务是失败的,就是失败退出
*/
System.exit(runStatus);
}
}
- 查看运行结果
符合预期结果。
这样就简单地实现了map端join的案例。