关于MapReduce

MapReduce其实是一种可用于数据处理的编程模型。Hadoop中可以运行各个语言版本的MapReduce程序,但是一般来说还是常用Java语言。最重要的是,MapReduce程序本质上是并行运行的,因此可以将大规模的数据分析任务分发给任何一个拥有足够多机器的数据中心。MapReduce的优势就在于处理大规模数据集。

MapReduce任务过程分为两个处理阶段:map阶段和reduce阶段。每个阶段都是以<key, value>键-值对作为输入和输出。当然还需要有map函数和reduce函数。

举个简单的例子:当我们需要去做一盘西红柿炒鸡蛋的时候,我们首先需要去做的就是处理食材(假设食材都已经存在且足够了)。

map(映射)阶段:我们需要西红柿剥皮切开,鸡蛋打开搅拌好,以及各种油盐酱油等配料准备好,这就相当于是map阶段。在这个阶段当中,我们会对数据进行相应的处理,如果存在烂西红柿或者坏鸡蛋(脏数据或者空值数据等),map阶段将会过滤掉这些数据。当我们只有处理好所有的食材(数据)之后才能进行炒菜。即只有当所有的map阶段完成之后,才会进行reduce阶段。

reduce(化简)阶段:简单来说就是得到map阶段的输出结果的一个汇总,进行进一步的数据分析。

Hadoop中的MapReduce

定义一些术语。MapReduce作业(job)是客户端需要执行的一个工作单元:包括输入数据、MapReduce程序和配置信息。Hadoop将作业分成若干个任务(task)来执行,其中包括两类任务map任务和reduce任务。这些任务运行在集群的节点上,并通过YARN进行调度。如果一个任务失败,将在另一个不同的节点上自动重新调度。

Hadoop将MapReduce的输入数据划分成等长的数据块,称为输入分片。一般分片大小趋向于HDFS的一个块的大小,默认是128MB。并且Hadoop会在存储由输入数据(HDFS中的数据)的节点上运行map任务,可以获得最佳性能,因为不需要使用集群的带宽资源,即“数据本地化优化”。

如果有好多reduce任务,每个map任务将会针对输出进行分区,即为每个reduce任务建一个分区。每个分区有很多键,但每个键对应的键值对都记录在同一分区。分区通常用默认的partitioner通过哈希函数来分区,很高效。

hadoop map 偏移量 hadoop中mapreduce实例_hadoop

这里采用的是《Hadoop权威指南》书中第二章的例子。

获取数据集(这里是参考其他博主的代码)

#!/bin/bash

# 下载数据集
for i in `seq 1929 2019`
do
    wget ftp://ftp.ncdc.noaa.gov/pub/data/gsod/$i/gsod_$i.tar
done

# 数据格式整理
for i in `seq 1929 2019`
do
    tar -xf gsod_$i.tar
    gzip -d *.gz
    cat *.op | grep -v "^STN" > $i.txt
    sed -i 's/[ ][ ]*/ /g' $i.txt
    rm *.op
done

 

使用Hadoop分析数据

构造MaxTemperature.java文件

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.FloatWritable;
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;

public class MaxTemperature {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		
		//Job对象指定作业执行规范,进行控制整个作业的运行。
		Job job = new Job(); 
		//在Hadoop集群上运行不必明确指定jar文件,只需要传递一个类。
		job.setJarByClass(MaxTemperature.class);
		job.setJobName("Max temperature");
		//调用静态方法addInputPath来定义输入数据的路径,可以实现多路经的输入
		FileInputFormat.addInputPath(job, new Path("./input"));
		//指定输出路径,并且只能有一个输出路径,在运行作业前该目录不应该存在,否在Hadoop会报错并拒绝运行作业
		//这是防止数据丢失(防止长时间运行的作业结果被意外覆盖)
		FileOutputFormat.setOutputPath(job, new Path("./output"));
		job.setMapperClass(MaxTemperatureMapper.class);
		job.setReducerClass(MaxTemperatureReducer.class);
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(FloatWritable.class);
		//题外话:setOutputKeyClass()和setOutputValueClass()方法控制reduce函数
		//的输出类型,并且必须和Reduce类相匹配。map函数的输出类型默认情况下和reduce函数
		//相同,不需要单独设置。但是,如果不同,则必须通过setMapOutputKetClass()和
		//setMapOutputValueClass()方法来设置map函数的输出类型。
		
		//job.waitForCompletion()提交作业并等待执行完成,唯一参数是一个标识,指示是否已生成详细输出。 
		System.exit(job.waitForCompletion(true) ? 0: 1);
	}

}

构造MaxTemperatureMapper.java文件

import java.io.IOException;

import org.apache.hadoop.io.FloatWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

public class MaxTemperatureMapper
	extends Mapper<LongWritable, Text, Text, FloatWritable> {
	private static final int MISSING = 999;
	
	@Override
	public void map(LongWritable key, Text value, Context context)
		throws IOException, InterruptedException {
		String[] arr = value.toString().split(" ");
		
		float airTemperature = Float.parseFloat(arr[3]);
		int year = Integer.parseInt(arr[2]) / 10000;
		context.write(new Text(Integer.toString(year)), new FloatWritable(airTemperature));
	}
}

构造MaxTemperatureReducer.java文件

import java.io.IOException;

import org.apache.hadoop.io.FloatWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

public class MaxTemperatureReducer extends Reducer<Text, FloatWritable, Text, FloatWritable> {
	
	@Override
	public void reduce(Text key, Iterable<FloatWritable> values, Context context)
		throws IOException, InterruptedException {
		
		float maxValue = Integer.MIN_VALUE;
		for (FloatWritable value : values) {
			maxValue = Math.max(maxValue, value.get());
		}
		context.write(key, new FloatWritable(maxValue));
	}
}

combiner函数

由于集群上的可用带宽限制了MapReduce作业的数量,因此尽量避免map和reduce任务之间的数据传输是有利的。combiner函数的规则制约着可用的函数类型。因为combiner函数的输出将会作为reduce函数的输入,并且由于combiner函数属于优化方案,不会影响reducer的输出结果。

举例说明combiner函数作用,找出每年最高温度的情景:

没有combiner函数

hadoop map 偏移量 hadoop中mapreduce实例_hadoop map 偏移量_02

 

有combiner函数

hadoop map 偏移量 hadoop中mapreduce实例_apache_03

可以看出combiner有效的减少了map和reduce任务之间的数据传输带宽,但是combiner并不能满足所有的场景,例如求每年的平均气温就不能这样使用。

 

 

参考

  1. Hadoop权威指南