系列文章目录
简单概述
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
, 其中Mapper
和Reducer
分别是执行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
, 然后注册好Mapper
与Reducer
// 设置Mr任务执行时执行M和R任务的类
job.setMapperClass(MaxTemperatureMapper.class);
job.setReducerClass(MaxTemperatureReducer.class);
接下来我们要设置map任务输出的键值对以及reducer任务输出的键值对是什么类型, 这部分可以参考我们之前重写的map
和reduce
方法中key
与values
的类型.
// 设置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可以看到当前任务的job
的id
, 当然, 下面还有一些其他的信息, 这个就是进行一次mapreduce所打印出来的日志.
接下来我们就可以去/opt/data/output/temperature
查看我们计算后获得的结果了, 但是我们进入目录后会发现只有两个文件如下图, part-r-00000
以及_SUCCESS
, 好像并没有我们需要的结果, 其实这里part-r-00000
内就是reduce
任务之后的结果, 由于这里只有一个reduce
任务因此只产生了一个文件.
通过cat part-r-00000
我们可以查看到计算后的每个天气的最大温度:
到这里我们就实现了一个最大温度的计算程序, 虽然数据量有些小, 但是却详细的说明了我们如果要实现一个mapreduce
程序应该怎么办, 有助于我们理解mapreduce
的执行逻辑.
补充
后期会补充一个部分的内容, 也就是Main
函数上job.setCombinerClass(MaxTemperatureReducer.class);
的作用.
总结
在这个mapreduce
程序中, 我们明白了如何定义一个map
任务以及一个reduce
任务, 以及如何执行一个mapreduce
作业, 对于map
任务我们不需要考虑整个数据源的操作, 而是要考虑拿到一部分数据对此部分数据我们该如何将其获取并转换成键值对的形式, 同理, reduce
也是在拿到map
任务产生的中间结果键值对, 如何对每一个键值对进行操作, 获得我们需要的结果.