Hadoop3.x中MapReduce实现Map端join案例

110 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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的案例。