【Hadoop权威指南】实现一个最大温度查询程序

系列文章目录



简单概述

Hadoop权威指南在第2章部分举了一个实现最大温度查询的程序, 这里对程序进行了修改, 将代码变得更加好理解, 能够更加简单的实现一个最大温度查询程序.

程序配置

pom.xml的依赖

本文使用的hadoop版本是3.1.1, 因此创建的maven项目中在pom.xml中添加如下依赖:

<!-- 在project标签内进行添加, 并且这里是包含了dependencies标签 -->
<dependencies>
		<!-- 基础支持 -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!-- hdfs支持 -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>3.1.1</version>
        </dependency>
        <!-- 客户端 -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.1.1</version>
        </dependency>
</dependencies>

数据集

2021-12-20	13
2021-12-20	14
2021-12-20	15
2021-12-20	17
2021-12-20	10
2021-12-21	10
2021-12-21	12
2021-12-21	12
2021-12-21	15
2021-12-22	8
2021-12-22	13
2021-12-22	14
2021-12-22	11
2021-12-22	6
2021-12-23	13
2021-12-23	17
2021-12-24	13
2021-12-24	19
2021-12-24	10
2022-12-24	13
2022-12-24	19
2022-12-24	10
2022-12-24	22
2022-12-24	30
2022-12-24	40

代码编写

这里将程序写在了一个.java文件中, 整体的过程如下:

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;


import java.io.File;
import java.io.IOException;


public class MaxTemperature {
    public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        String inputPath = args[0];
        String outputPath = args[1];

        // 如果args.length != 2 那么说明缺少路径地址
        if (args.length != 2) {
            System.err.println("Usage: MaxTemperature <input path> <output path>");
            System.exit(-1);
        }

        // 创建新的任务, 这里指定任务的名字
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "MaxTemperature");

        // 设置Mr任务执行时执行M和R任务的类
        job.setMapperClass(MaxTemperatureMapper.class);
        // combiner
        job.setCombinerClass(MaxTemperatureReducer.class);


        job.setReducerClass(MaxTemperatureReducer.class);

        // 设置map任务输出泛型
        job.setMapOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 设置reduce任务的输出泛型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 设置输出路径和输入路径
        FileInputFormat.setInputPaths(job, new Path(inputPath));
        FileOutputFormat.setOutputPath(job, new Path(outputPath));

        // 设置运行模式
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }

    static class MaxTemperatureMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 按照空格进行切割
            String[] sp = value.toString().split("\\s+");
            // 日期
            Text k = new Text(sp[0]);
            // 温度
            IntWritable v = new IntWritable(Integer.parseInt(sp[1]));

            // map主要是构建一个k, v结构, 类似与hashmap, 构建完成后, 交给Reducer进行处理, 获取我们需要的值
            // 数据写入
            context.write(k, v);
        }
    }

    static class MaxTemperatureReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int maxValue = Integer.MIN_VALUE;
            for (IntWritable value : values) {
                // 将值取出
                int num = value.get();
                // 比较找出最大的温度
                maxValue = Math.max(maxValue, num);
            }
            // 从这里发现, Reducer的逻辑是每一次任务当中的逻辑, 也就是说写的是一个key当中应该做什么事情
            IntWritable v = new IntWritable(maxValue);
            context.write(key, v);
        }
    }
}

代码解读

整个代码拥有三个类MaxTemperature, MaxTemperatureMapper, MaxTemperatureReducer, 其中MapperReducer分别是执行Map任务和Reduce任务的方法.

MaxTemperatureMapper

 static class MaxTemperatureMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 按照空格进行切割
            String[] sp = value.toString().split("\\s+");
            // 日期
            Text k = new Text(sp[0]);
            // 温度
            IntWritable v = new IntWritable(Integer.parseInt(sp[1]));

            // map主要是构建一个k, v结构, 类似与hashmap, 构建完成后, 交给Reducer进行处理, 获取我们需要的值
            // 数据写入
            context.write(k, v);
        }
    }

MaxTemperatureMapper类重写了map方法, 我们重写的逻辑是获取一行当中的数据后我们该如何进行操作,
当我们拿到源头数据2021-12-20 13后, 首先我们将其转成字符串, 并按照空格进行切割, 也就是String[] sp = value.toString().split("\\s+");, 之后我们从sp[0]中拿到日期, 从sp[1]中拿到温度, 以日期为键, 以温度为值, 按键值对的方式写出, 最终源数据产生的map可能是如下的形式:

key			value -> 实际是个迭代器,这里只是为了表达方便这样写
2021-12-20	13, 14, 15, 17, 10
2021-12-21	10, 12, 12, 15
2021-12-22	8, 13, 14, 11, 6
2021-12-23	13, 17
2021-12-24	13, 19, 10, 13, 19, 10, 22, 30, 40

MaxTemperatureReducer

static class MaxTemperatureReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            int maxValue = Integer.MIN_VALUE;
            for (IntWritable value : values) {
                // 将值取出
                int num = value.get();
                // 比较找出最大的温度
                maxValue = Math.max(maxValue, num);
            }
            // 从这里发现, Reducer的逻辑是每一次任务当中的逻辑, 也就是说写的是一个key当中应该做什么事情
            IntWritable v = new IntWritable(maxValue);
            context.write(key, v);
        }
    }

MaxTemperatureReducer类重写了reduce方法, 这时候我们获取的数据是map任务输出后的结果, 因此我们最终拿到的是键值对的形式, 可以发现这里的值类型是一个迭代器, 这是因为map最终将相同键的值全部存放在一起, 因此我们这里重写的reduce任务, 是对一个键值对的操作, 我们将值取出进行比较找到最大的值, 那么最终将其作为新的值和键重新写出, 最终获取到当天最大的温度.

Main

public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
        String inputPath = args[0];
        String outputPath = args[1];

        // 如果args.length != 2 那么说明缺少路径地址
        if (args.length != 2) {
            System.err.println("Usage: MaxTemperature <input path> <output path>");
            System.exit(-1);
        }

        // 创建新的任务, 这里指定任务的名字
        Configuration conf = new Configuration();
        Job job = Job.getInstance(conf, "MaxTemperature");

        // 设置Mr任务执行时执行M和R任务的类
        job.setMapperClass(MaxTemperatureMapper.class);
        // combiner
        job.setCombinerClass(MaxTemperatureReducer.class);


        job.setReducerClass(MaxTemperatureReducer.class);

        // 设置map任务输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 设置reduce任务的输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        // 设置输出路径和输入路径
        FileInputFormat.setInputPaths(job, new Path(inputPath));
        FileOutputFormat.setOutputPath(job, new Path(outputPath));

        // 设置运行模式
        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }

main方法部分主要是实现任务的注册, 在注册任务时, 我们首先要先创建一个job, 然后注册好MapperReducer

// 设置Mr任务执行时执行M和R任务的类
job.setMapperClass(MaxTemperatureMapper.class);
job.setReducerClass(MaxTemperatureReducer.class);

接下来我们要设置map任务输出的键值对以及reducer任务输出的键值对是什么类型, 这部分可以参考我们之前重写的mapreduce方法中keyvalues的类型.

// 设置map任务输出类型
job.setMapOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);

 // 设置reduce任务的输出类型
 job.setOutputKeyClass(Text.class);
 job.setOutputValueClass(IntWritable.class);

最后由于我们是进行本地执行, 所以需要设置一下任务从哪个地方获取源数据以及结果输出到哪儿, 这里我们会进行打包然后产生一个.jar文件, 因此我们通过args[0]args[1]获取源数据路径输出路径.

String inputPath = args[0];
String outputPath = args[1];

// 如果args.length != 2 那么说明缺少路径地址
if (args.length != 2) {
    System.err.println("Usage: MaxTemperature <input path> <output path>");
    System.exit(-1);
}
// 设置输出路径和输入路径
FileInputFormat.setInputPaths(job, new Path(inputPath));
FileOutputFormat.setOutputPath(job, new Path(outputPath));

最后一句System.exit(job.waitForCompletion(true) ? 0 : 1);则是对本地化的设定.

代码执行

由于这个代码是在虚拟机上跑的, 因此这里需要搭建一下虚拟机的集群, 详细的内容可以参考【Hadoop权威指南】Hadoop集群的安装部分的内容, 这里我们首先将代码文件进行打包操作, 获得一个.jar文件.
之后将需要的数据源和jar包传输到虚拟机中, 以下是我存放的地址位置:

output: 用于后期输出最终结果的地方
路径地址: /opt/data/output
input: 用于存放源数据的地方, 内部存放了一个temperature.txt, 里面写了上面的源数据
路径地址: /opt/data/input
local: 用于存放jar包, 这里面存放了打包好的jar包
路径地址: /opt/data/local

接下来通过执行如下的语句

# hadoop jar 是固定的参数
# /opt/data/local/*.jar是我的jar包位置
# MaxTemperature则是当时创建的java文件的class名, 用于调取里面的main方法(主程序入口)
# /opt/data/input/temperature.txt 则是获取源数据
# /opt/data/output/temperature 则是输出源数据计算后的结果, 
# 这里/temperature是最后其计算后生成的文件夹
# 这里的 \ 算是一种连接符保证其是一行命令执行, 如果报错, 可以将其拼接后在一行执行
# 我这里主要是为了能够方便查看
hadoop jar /opt/data/local/chapter02-1.0-SNAPSHOT.jar MaxTemperature \
/opt/data/input/temperature.txt \
/opt/data/output/temperature

执行会产生如下的内容, 通过job可以看到当前任务的jobid, 当然, 下面还有一些其他的信息, 这个就是进行一次mapreduce所打印出来的日志.
map的job
接下来我们就可以去/opt/data/output/temperature查看我们计算后获得的结果了, 但是我们进入目录后会发现只有两个文件如下图, part-r-00000以及_SUCCESS, 好像并没有我们需要的结果, 其实这里part-r-00000内就是reduce任务之后的结果, 由于这里只有一个reduce任务因此只产生了一个文件.
mapreduce结果
通过cat part-r-00000我们可以查看到计算后的每个天气的最大温度:
mapreduce计算后结果
到这里我们就实现了一个最大温度的计算程序, 虽然数据量有些小, 但是却详细的说明了我们如果要实现一个mapreduce程序应该怎么办, 有助于我们理解mapreduce的执行逻辑.

补充

后期会补充一个部分的内容, 也就是Main函数上job.setCombinerClass(MaxTemperatureReducer.class);的作用.

总结

在这个mapreduce程序中, 我们明白了如何定义一个map任务以及一个reduce任务, 以及如何执行一个mapreduce作业, 对于map任务我们不需要考虑整个数据源的操作, 而是要考虑拿到一部分数据对此部分数据我们该如何将其获取并转换成键值对的形式, 同理, reduce也是在拿到map任务产生的中间结果键值对, 如何对每一个键值对进行操作, 获得我们需要的结果.

参考文献

1.hadoop权威指南

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值