MapReduce编程: 3. 航班数据按月份升序,星期降序排列

MapReduce编程:3. 航班数据按月份升序,星期降序排列

题目
  • 航空公司数据按月份升序,同时按星期降序排列
  • 任意数量的Reducer
  • 输出文件全排序:Part-0000,Part-0001,…按文件名依次接将得到一个完整的结果排序
  • 输入一下字段逗号隔开:月份,星期几,起飞机场,目的机场
分析
  1. 题目要求只要实现排序,所以不存在计算的问题,但是要使用两个字段来排序,所以需要自己定义数据类型,来作为map输出的键,而后的起飞机场和目的机场也是需要自己定义数据类型的,所以首先需要了解Writable接口和WritableComparable接口
  2. https://blog.csdn.net/Dongroot/article/details/88571680 这篇博客中,我输出了一次中间的处理结果,可以看到,hadoop对自带的类是有默认排序的,所以对于自定义的类,hadoop肯定无法默认排序了,所以需要自己定义排序规则
  3. 在自定义的数据类型可以进行排序之后,hadoop会根据排序规则去进行排序,这时map和reduce不在需要做别的处理,只需要在map将数据分成键值对,在reduce将结果输出,但是我们要使用多reduce对排序进行并行处理,所以还需要了解Partitioner类
Writable接口和WritableComparable接口
  1. 在hadoop中,集群环境下,处理数据是在不同的计算机,机器间的数据流通必须要通过网络,所以hadoop提供了Writable接口,来实现数据的序列化与反序列化,这个原理我知道,但是说不清楚,就和做javaweb开发的时候,Javabean要实现Serializable接口一样的道理。所以只要记住,要自己定义数据类型,必须要实现Writable接口
  2. 同时,MapReduce的输入输出都是键值对形式的,在shuffle阶段,会对键值对根据键的数据类型,进行排序,所以作为键的类,还必须要实现Comparable接口,来定义排序规则
  3. 所以hadoop提供了WritableComparable接口,实现这个接口,相当于同时实现了Writable和Comparable接口
  4. Writable接口有两个方法,readFields(DataInput in)和write(DataOutput out),前者是从输入流中抓取数据,重新创建Writable实例,DataInput中的方法可以反序列化java的基本类型;后者是将实例写入到输出流中,DataOutput中的方法可以序列化java的基本类型
  5. WritableComparable接口多了一个CompareTo(Object o)方法,作用就是定义排序规则,将调用者与参数进行对比,返回值为int型,大于返回大于零的数,等于返回0,小于返回小于零的数
  6. 还看不懂的话,就没办法了,推荐个博客

https://blog.csdn.net/chengyuqiang/article/details/78634755

Partitioner类
  1. 在前面的两个博客中,我都只使用了一个reduce进行操作,很显然没有发挥出MapReduce的全部实力,所以这次我们使用多个reduce进行并行处理,那么问题来了,每个reduce会产生一个输出文件,那么hadoop是如何知道map的结果应该发往哪个reduce进行处理,这就需要指定分区
  2. hadoop自带了几个Partitioner,在不指定的情况下,hadoop默认使用HashPartitioner,根据哈希算法计算分区,还有一种是TotalOrderPartitioner,分区中的数据范围需要通过分区文件来确认
  3. 对于TotalOrderPartitioner,分区文件可以自己手动创建,进行等距划分,但是如果数据本身是分布不均匀的,会导致有些reduce工作量大,有些工作量小,所以还可以进行数据抽样,然后根据抽样结果的数据分布,创建分区文件
  4. 除了这两种Partitioner,还可以自己定义,只要继承Partitioner类,实现getPartition方法,该方法返回值为int型,表示发往第几个reduce,从0开始
  5. 推荐个博客,我只能解释到这了

https://blog.csdn.net/baolibin528/article/details/50801604

准备工作
  • ubuntu14环境
  • 启动的hadoop
  • 安装hadoop插件的eclipse
  • 一个原始数据集

对于前三个,还没弄好的话就去看我之前的博客,这次的原始数据集只需要四列数据,如图:
在这里插入图片描述

分别是月份,星期,起飞机场,目的机场,我们需要关注的只是前两列数据,数据之间用逗号隔开。
文件创建好后,上传到HDFS,然后打开eclipse,创建MapReduce Project

上代码
  1. 首先创建我们自己定义的键的数据类型
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.WritableComparable;

public class OutputKey implements WritableComparable<OutputKey>  {

	// 月份
	private String month;
	// 星期
	private String week;
	private static final List<String> WEEKS = new ArrayList<String>(){
		// 使用内部类进行初始化
		{
			this.add("Mon");
			this.add("Tues");
			this.add("Wed");
			this.add("Thur");
			this.add("Fri");
			this.add("Sat");
			this.add("Sun");
		}
	};
		
	public OutputKey(){
		
	}
	
	public OutputKey(String m , String w){
		month = m;
		week = w;
	}
	
	public String getMonth() {
		return month;
	}

	public void setMonth(String month) {
		this.month = month;
	}

	public String getWeek() {
		return week;
	}

	public void setWeek(String week) {
		this.week = week;
	}

	@Override
	public void readFields(DataInput in) throws IOException {
		Text text = new Text();
		text.readFields(in);
		String[] keys = text.toString().split("\t");
		this.month = keys[0];
		this.week = keys[1];
	}

	@Override
	public void write(DataOutput out) throws IOException {
		Text text = new Text(month+"\t"+week);
		text.write(out);
	}

	@Override
	public int compareTo(OutputKey o) {
		if(this.month.compareTo(o.getMonth()) > 0) {
			return 1;
		}
		else if(this.month.compareTo(o.getMonth()) < 0) {
			return -1;
		}
		else if(WEEKS.indexOf(this.week) > WEEKS.indexOf(o.getWeek())){
			return -1;
		}
		else if(WEEKS.indexOf(this.week) < WEEKS.indexOf(o.getWeek())){
			return 1;
		}
		return 0;
	}

	@Override
	public String toString() {
		return month + "\t" + week ;
	}
}

解释一个地方,在readFields和write方法里面,我借助了Text类的方法,对输入输出流进行操作,因为Text类是hadoop自带的已经实现了Writable接口的类,所以封装好的东西为什么还要自己再重新写一遍,自己写也可以,不嫌麻烦就自己写吧

  1. 然后创建我们自己定义的值的数据类型
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;

public class OutputValue implements Writable {

	// 起飞机场
	private String start;
	// 目的机场
	private String end;
	
	public OutputValue() {
		
	}
	
	public OutputValue(String s,String e) {
		this.start = s;
		this.end = e;
	}
	
	public String getStart() {
		return start;
	}

	public void setStart(String start) {
		this.start = start;
	}

	public String getEnd() {
		return end;
	}

	public void setEnd(String end) {
		this.end = end;
	}

	@Override
	public void readFields(DataInput in) throws IOException {
		Text text = new Text();
		text.readFields(in);
		String[] keys = text.toString().split("\t");
		this.start = keys[0];
		this.end = keys[1];
	}

	@Override
	public void write(DataOutput out) throws IOException {
		Text text = new Text(start+"\t"+end);
		text.write(out);
	}

	@Override
	public String toString() {
		return start + "\t" + end;
	}
}
  1. 然后创建Mapper类
import java.io.IOException;

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

public class MyMapper extends Mapper<Object, Text, OutputKey, OutputValue> {

	@Override
	protected void map(Object key, Text value, Mapper<Object, Text, OutputKey, OutputValue>.Context context)
			throws IOException, InterruptedException {
		/*
		 * map输入:
		 *   原始文本
		 *   
		 * map输出:
		 *   key2,(月份,星期)
		 *   value2,(起飞地,目的地)
		 */
		
		String[] values = value.toString().split(",");
		context.write(new OutputKey(values[0], values[1]), new OutputValue(values[2],values[3]));
	}
}
  1. 创建Reducer类
import java.io.IOException;

import org.apache.hadoop.mapreduce.Reducer;

public class MyReducer extends Reducer<OutputKey, OutputValue	, OutputKey, OutputValue> {

	@Override
	protected void reduce(OutputKey outputKey, Iterable<OutputValue> iterable, Reducer<OutputKey, OutputValue, OutputKey, OutputValue>.Context context)
			throws IOException, InterruptedException {
		/*
		 * reduce输入:
		 *   key2,(月份,星期)
		 *   value2,(起飞地,目的地)
		 *   
		 * reduce输出:
		 *   无
		 */
		
		for(OutputValue outputValue: iterable) {
			context.write(outputKey, outputValue);
		}
	}
}
  1. 接下来创建Partitioner类
import org.apache.hadoop.mapreduce.Partitioner;

public class MyPartitioner extends Partitioner<OutputKey, OutputValue> {

	@Override
	public int getPartition(OutputKey outputKey, OutputValue outputValue, int numPartitions) {
		int result = (Integer.parseInt(outputKey.getMouth()) - 1) / 3;
		return result;
	}
}

这里说一下,我没有采用抽样,而是直接定义分区,每个reduce处理3个月的数据,一共四个reduce并行处理

  1. 最后创建驱动程序,先使用一个reduce处理
import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;

public class Sort {
	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		String[] path = {
				"hdfs://localhost:9000/user/hduser/sort/input",
				"hdfs://localhost:9000/user/hduser/sort/output"
		};
		
		Configuration conf = new Configuration();
		
		Job job = Job.getInstance(conf);
		job.setJarByClass(Sort.class);
		job.setOutputKeyClass(OutputKey.class);
		job.setOutputValueClass(OutputValue.class);
		job.setMapperClass(MyMapper.class);
		job.setReducerClass(MyReducer.class);
		job.setInputFormatClass(TextInputFormat.class);
		job.setOutputFormatClass(TextOutputFormat.class);
		
		// 设置四个reduce同步执行
//		job.setNumReduceTasks(4);
		// 设置Partitioner
//		job.setPartitionerClass(MyPartitioner.class);
		
		FileInputFormat.setInputPaths(job, new Path(path[0]));
		
		FileSystem fs = FileSystem.get(conf);
		Path p = new Path(path[1]);
		if(fs.exists(p)) {
			// 目录存在,删除
			fs.delete(p, true);
		}
		FileOutputFormat.setOutputPath(job, p);
		
		boolean a = job.waitForCompletion(true);
		if(a) {
			System.exit(0);
		}
		else {
			System.exit(1);
		}
	}
}

这里需要说一下,因为之前都是每次运行完想要再次运行就需要自己手动去删除output文件夹,所以这次就直接在程序里,对HDFS的文件夹进行了操作,如果每次运行output文件夹都在,就会自动删除

  1. 查看结果
    在这里插入图片描述
    在这里插入图片描述
    可以看到只使用一个reduce,只会产生一个结果文件,并且所有的数据都在文件中,文件根据月份升序,同时星期降序排序

  2. 接下来我们使用四个reduce进行并行处理,将
    在这里插入图片描述

这两行注释放开,然后运行

  1. 查看结果
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

可以看到,首先产生了四个输出文件,而且按照我定义的分区规则,每三个月的数据在一个文件中,在每个文件中又都是符合排序规则的,四个文件接起来也是符合全排序的

写在最后的话

因为我是自己定义的分区,因为我们的数据量不大,又是伪分布式,所以如果想尝试数据抽样,然后根据数据分布自动创建分区的话,推荐个博客,自己看吧,我就不弄了

https://blog.csdn.net/u011596455/article/details/78069824

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值