MapReduce程序编写

Hadoop支持多种语言开发MapReduce程序,但是对JAVA语言的支持最好。编写一个MapReduce程序需要新建三个类:Mapper类、Reduce类、驱动类。Mapper类和Reduce类也可以作为内部类放在程序执行主类中。

MapReduce程序内置数据类型

Hadoop提供了一系列内置数据类型,这些数据类型均实现了WritableComparable接口,可以被序列化进行网络传输和文件存储以及比较大小。

Java 类型

Hadoop Writable 类型

Boolean

BooleanWritable

Byte

ByteWritable

Int

IntWritable

Float

FloatWritable

Long

LongWritable

Double

DoubleWritable

String

Text

Map

MapWritable

Array

ArrayWritable

Null

NullWritable

MapReduce 编程规范

Mapper阶段:

1.用户自定义的Mapper要继承自己的父类。

2.Mapper的输入数据是KV对的形式(KV的类型可自定义)。

3.Mapper中的业务逻辑写在map()方法中。

4.Mapper的输出数据是KV对的形式(KV的类型可自定义)。

5.map()方法(MapTask进程)对每一个调用一次。

Mapper是MapReduce提供的泛型类,继承Mapper需要传入4个泛型参数,前两个参数为输入key和value的数据类型,后两个参数为输出key和value的数据类型。

例如

public class MyMapper extends Mapper {  //自定义Mapper类
	public void map(LongWritable key, Text value, Mapper.Context context)
			throws IOException, InterruptedException {

	}
}

上述代码中的map()方法有三个参数,解析如下:

LongWritable key:输入文件中每一行的起始位置。即从输入文件中解析出的对中的key值。

Text value:输入文件中每一行的内容。即从输入文件中解析出的对中的value值。

Context context:程序上下文,用来传递数据以及其他运行状态信息,map中的key、value写入context,传递给Reducer进行reduce。

Reduce阶段:

1.用户自定义的Reducer要继承自己的父类。

2.Reducer的输入数据类型对应Mapper的输出数据类型,也是KV。

3.Reducer的业务逻辑写在reduce()方法中。

4.ReduceTask进程对每一组相同k的组调用一次reduce()方法。

与Mapper相似,Reducer是MapReduce提供的泛型类,继承Mapper需要传入4个泛型参数,前两个参数为输入key和value的数据类型,后两个参数为输出key和value的数据类型。

例如:


public class MyReducer extends Reducer{ //自定义Reducer类
	public void reduce(Text key,Iterable values,
			Reducer.Context context)
			throws IOException,InterruptedException{
	
	}
}

上述代码中的reduce()方法有三个参数,解析如下:

Text key:Map任务输出的key值,即接受到的对中的key值。

Iterable values:Map任务输出的value值的集合(相同key的集合),即接受到的对中的value-list集合。

Context context:程序上下文,用来传递数据以及其他运行状态信息,reduce进行处理之后数据继续写入context,继续交给Hadoop写入hdfs系统。

驱动类:

MapReduce程序入口类,主要用于启动一个MapReduce作业。

相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象。需要配置Mapper类、Reducer类、Map任务输出类型、Reduce任务输出类型、输入文件格式、输出文件格式、输入文件路径、输出文件路径等信息。

例如:

package com.mapreduce;
public class Main {
	public static void main(String[] args) throws Exception {
		Configuration conf = new Configuration();//初始化Configuration类
		//构建任务对象
		Job job = Job.getInstance(conf, Main.class.getName());
		job.setJarByClass(Main.class);
		//设置Mapper类,Reducer类
		job.setMapperClass(MyMapper.class);
		job.setReducerClass(MyReducer.class);
		//设置Map任务输出类型,与map()方法一致
		job.setMapOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);
		//设置Reduce任务输出类型,与reduce()方法一致
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);
		//设置输入、输出文件格式
		job.setInputFormatClass(TextInputFormat.class);
		job.setOutputFormatClass(TextOutputFormat.class);
		//设置需要统计的文件的输入路径
        FileInputFormat.addInputPath(job,new Path("/input/"));
        FileOutputFormat.setOutputPath(job,new Path("/output/"));
		//提交给hadoop集群
		System.exit(job.waitForCompletion(true) ? 0 : 1);
	}
}

提交程序之前需要启动Hadoop集群,包括HDFS和YARN,因为HDFS存储了MapReduce程序的数据来源,而YARN则负责MapReduce任务的执行、调度以及集群的资源管理。将包含自定义的Mapper类、Reducer类和驱动类的JAVA项目打包为jar包并上传到集群的NameNode节点上,提交任务到集群运行程序。

命令为:

hadoop jar Main.jar com.mapreduce.Main

Main.jar为打包后的jar包名,com.mapreduce.Main为驱动类的包名和类名。

setup方法和cleanup方法

Mapper类和Reducer类都有setup方法和cleanup方法。

setup方法和cleanup方法默认是不做任何操作,且它们只被执行一次。setup方法一般会在map函数之前执行一些准备工作,如作业的一些配置信息,数据初始化等,也就是说setup方法在map方法或者reduce方法之前执行,cleanup方法是完成一些结尾清理的工作,如:资源释放等。也就是说,cleanup方法是在map方法或者reduce方法之后执行。

InputFormat和OutputFormat

InputFormat主要用于对输入数据的描述。

InputFormat主要功能有两个,一是按照某个策略,将输入数据切分为若干个split,Map任务的个数和split的个数相对应。Inputformat中对应getSplits的方法,完成数据切分的功能。二是为Mapper提供输入数据。通过某个给定的split,能够将其解析成一个个的key/value对。inputformat中另外一个方法是getRecordReader,通过传入inputsplit,返回recordReader对象。Map任务执行过程中,就是通过不断的调用RecordReader的方法迭代获取key/value.

InputFormat默认使用的实现类是:TextInputFormat。TextInputFormat 的功能逻辑是:一次读一行文本,然后将该行的起始偏移量作为key,行内容作为 value 返回。

OutputFormat主要用于对输出数据的描述。

OutputFormat中主要包括两个方法。一是getRecoreWrite方法,用于返回一个RecordWriter的实例,Reduce任务在执行的时候就是利用这个实例来输出Key/Value的。(如果Job不需要Reduce,那么Map任务会直接使用这个实例来进行输出。)二是checkOutputSpecs方法,主要检查输出目录是否合法,一般在作业提交之前会被调用,如果目录已经存在就会抛出异常,放置文件被覆盖。

OutputFormat默认实现类是 TextOutputFormat,功能逻辑是:将每一个 KV 对,向目标文本文件输出一行。

Partition 分区

Partition :用来指定map输出的key交给哪个reuducer处理。

默认情况下,作业的ReduceNum=1,每一个Reduce对应生成一个结果文件。如果ReduceNum=0,则没有reduce阶段。

默认分区是根据key的hashCode对ReduceTasks个数取模得到的。用户没法控制哪个key存储到哪个分区。所以想要控制key到哪一个分区,需要自定义分区。

自定义类继承Partitioner,重写getPartition()方法。

public class MyPartitioner extends Partitioner{
	//继承Partitioner抽象类
	@Override
	public int getPartition(IntWritable key, IntWritable value, int numPartitions) {
		if(key.get()==1){
			return 0;    //指定结果到第0个分区,part-r-00000
		}else if(key.get()==2){
			return 1;    //part-r-00001
		}else{
			return 2;    //part-r-00002
		}
	}	
}

在Job驱动中,设置自定义Partitioner。

job.setPartitionerClass(MyPartitioner.class)

自定义Partition后,要根据自定义Partitioner的逻辑设置相应数量的ReduceTask。

job.setNumReduceTasks(5);

1.如果ReduceTask的数量> getPartition的结果数,则会多产生几个空的输出文件part-r-000xx。

2.如果1

3.如果ReduceTask的数量=1,则不管MapTask端输出多少个分区文件,最终结果都交给这一个ReduceTask,最终也就只会产生一个结果文件 part-r-00000。

4.分区号必须从零开始,逐一累加。

Comparable排序

当我们用自定义的对象作为 key 来输出时,就必须要实现 WritableComparable 接口,重写其中的 compareTo()方法。

例如二次排序:

@Override
public int compareTo(Keypair o) {
		int res = this.first.compareTo(o.first);// 按照第一个字段进行排序
		if (res != 0)
			return res;
		else
			return Integer.valueOf(this.second).compareTo(Integer.valueOf(o.getSecond()));
	}

Combiner 合并

Combiner 合并可以提高程序执行效率,减少 IO 传输。但是使用时必须不能影响原有的业务处理结果。主要是实现本地相同key的合并,对输出的key排序,对value进行本地聚合运算。与reduce函数的形式相同(其实就是Reducer的一个实现),输出类型是中间的键值对类型,输入给reduce函数做最终的处理。

Combiner函数没有默认实现,只能用于Reduce的输入键值对与输出键值对完全一致,且不影响最终结果的场景,如累加、求最大值。

自定义一个 Combiner 继承 Reducer,需要重写 Reduce 方法。


public class MyCombiner extends Reducer{ //自定义Reducer类
	public void reduce(Text key,Iterable values,
			Reducer.Context context)
			throws IOException,InterruptedException{

	}
}

在 Job 驱动类中设置

job.setCombinerClass(MyCombiner.class);

你可能感兴趣的:(大数据,mapreduce,hadoop)