8.4 MapReduce 三大组件(二):Sort

任务目的

  • 理解流量统计项目案例的业务逻辑
  • 学会利用 WritableComparable 接口实现自定义排序
  • 掌握流量统计项目案例 Map 和 Reduce 端的自定义业务逻辑的编写
  • 熟记 MapReduce Driver 端编程规范

任务清单

  • 任务1:流量统计项目案例
  • 任务2:WritableComparable 排序
  • 任务3:MapReduce 编程

详细任务步骤

任务1:流量统计项目案例

  (1)数据样例

13726238888 2481 24681 
13560436666 1116 954 
13726230503 2481 24681 
13826544101 264 0 
13926435656 132 1512 
13926251106 240 0 
18211575961 1527 2106

  (2)字段释义

字段中文释义 字段英文释义 数据类型
手机号 phone String
上行流量 upflow Long
下行流量 downflow Long

  (3)项目需求二

  得出上题结果的基础之上再加一个需求:将统计结果按照总流量倒序排序。

  期望输出数据格式:

13502468823	101663100	1529437140	1631100240
13925057413	153263880	668647980	821911860
13726238888	34386660	342078660	376465320
...

  (4)项目解析

  基本思路:实现自定义的 bean 来封装流量信息,并将 bean 作为 Map 输出的 key 来传输。

  MapReduce 程序在处理数据的过程中会对数据排序(Map 输出的 kv 对传输到 Reduce 之前,会排序),排序的依据是 Map 输出的 key, 所以,我们如果要实现自己需要的排序规则,则可以考虑将排序因素放到 key 中。

任务2:WritableComparable 排序

  排序是 MapReduce 框架中最重要的操作之一。 MapTask 和 ReduceTak 均会对数据按照 key 进行排序。该操作属于 Hadoop 的默认行为,任何应用程序中的数据均会被排序,而不管逻辑上是否需要。

  默认排序是按照字典顺序排序

  对于 MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序

  对于 RecduceTak,它从每个 MapTask 上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask 统一对内存和磁盘上的所有数据进行一次归并排序

2.1 排序分类

  (1)部分排序

  MapReduce 根据输入记录的键对数据集排序,保证输出的每个文件内部有序。

  (2)全排序

  最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask

  但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了 MapReduce 所提供的并行架构。

  (3)辅助排序:(GroupingComparator分组)

  在Reduce端对key进行分组。

  应用于:在接收 key 为 bean 对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一个reduce()方法时,可以采用分组排序。

  (4)二次排序

  在自定义排序过程中,如果 compareTo() 方法中的判断条件为两个即为二次排序。

2.2 自定义排序 WritableComparable

  自定义 bean 对象将其作为 Map 输出的 key 来传输,需要实现 WritableComparable 接口重写 compareTo()方法,就可以实现排序。

  compareTo() 方法解析:

int compareTo(T o)

  参数:o表示要比较的对象。

  作用:比较此对象与指定对象的顺序。

  返回值:负整数、零或正整数,根据此对象是小于、等于还是大于指定对象。

  实现 WritableComparable 接口重写 compareTo()方法的具体实现:

public class FlowBeanSort implements WritableComparable<FlowBeanSort> {
	private long upFlow;     // 上行总流量
	private long downFlow;  // 下行总流量
	private long sumFlow;  // 总流量
	...

    // 自定义排序规则,按照总流量倒序排序
	@Override
	public int compareTo(FlowBeanSort o) {
		// 自定义降序排列
		return this.sumFlow > o.getSumFlow() ? -1 : 1;
	}
}

  也就是说当语句return this.sumFlow > o.getSumFlow() ? 1 : -1;的返回值为1时,即当this的值大于o的值时,compareTo是按照升序(由小到大)排序的。

  当返回值为-1时,也就是说this的值小于o的值时,compareTo是按照降序(由大到小)排序的。

  完整的 bean 程序如下所示:

package com.hongyaa.mr.sort;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.io.WritableComparable;

/**
 * 自定义一个 bean 类,实现 WritableComparable 接口重写 compareTo()方法,实现自定义排序
 * 
 * @author Administrator
 *
 */
public class FlowBeanSort implements WritableComparable<FlowBeanSort> {
	private long upFlow; // 总上行流量
	private long downFlow; // 总下行流量
	private long sumFlow; // 总流量

	// 无参构造方法必须有,目的是为了在反序列化操作创建对象实例时调用无参构造器
	public FlowBeanSort() {
		super();
	}

	// 序列化方法,目的是为了对象的初始化
	public FlowBeanSort(long upFlow, long downFlow, long sumFlow) {
		super();
		this.upFlow = upFlow;
		this.downFlow = downFlow;
		this.sumFlow = sumFlow;
	}

	public long getUpFlow() {
		return upFlow;
	}

	public void setUpFlow(long upFlow) {
		this.upFlow = upFlow;
	}

	public long getDownFlow() {
		return downFlow;
	}

	public void setDownFlow(long downFlow) {
		this.downFlow = downFlow;
	}

	public long getSumFlow() {
		return sumFlow;
	}

	public void setSumFlow(long sumFlow) {
		this.sumFlow = sumFlow;
	}

	// 序列化方法,将对象的字段信息写入输出流
	@Override
	public void write(DataOutput out) throws IOException {
		out.writeLong(upFlow);
		out.writeLong(downFlow);
		out.writeLong(sumFlow);
	}

	// 反序列化方法,从输入流中读取各个字段信息
	// 注意:字段的反序列化顺序需要和序列化的顺序保持一致,而且字段的类型和个数也要保持一致
	@Override
	public void readFields(DataInput in) throws IOException {
		this.upFlow = in.readLong();
		this.downFlow = in.readLong();
		this.sumFlow = in.readLong();
	}

	// 实现自定义排序,按照总流量倒序排序
	@Override
	public int compareTo(FlowBeanSort o) {
		// 自定义降序排列
		return this.sumFlow > o.getSumFlow() ? -1 : 1;
	}

	// 重写toString()方法
	@Override
	public String toString() {
		return upFlow + "\t" + downFlow + "\t" + sumFlow;
	}
}

任务3:MapReduce 编程

  要完成上述的需求,还需要完成三个程序分别是一个 Mapper 类、一个 Reducer 类和一个用于连接整个过程的驱动 Driver 主程序。

3.1 Map 端程序编写

  分析:以需求一的输出结果作为排序的输入数据,自定义FlowBeanSort,以 FlowBeanSort 为 Map 输出的 key,以手机号作为 Map 输出的 value,因为 MapReduce 程序会对 Map 阶段输出 的key 进行排序。具体实现如下所示:

// 输入数据是上一个统计程序的输出结果,已经是各个手机号的总流量信息
public class FlowSumSortMapper extends Mapper<LongWritable, Text, FlowBeanSort, Text> {

	@Override
	protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, FlowBeanSort, Text>.Context context)
			throws IOException, InterruptedException {
		// (1)获取一行文本的内容,并将其转换为String类型,之后按照分隔符“\t”进行切分
		String[] splits = value.toString().split("\t");
		// (2)取出手机号
		String telephone = splits[0];
		// (3)封装对象
		FlowBeanSort fbs = new FlowBeanSort();
		fbs.setUpFlow(Long.parseLong(splits[1]));
		fbs.setDownFlow(Long.parseLong(splits[2]));
		fbs.setSumFlow(Long.parseLong(splits[3]));
		// (4)将封装的fbs对象作为key,将手机号作为value,分发给Reduce端
		context.write(fbs, new Text(telephone));
	}
}
  • KEYIN:是指框架读取到的数据的key的类型,在默认的InputFormat下,读到的key是一行文本的起始偏移量,所以key的类型是Long,对应 Hadoop 中的 LongWritable
  • VALUEIN:是指框架读取到的数据的value的类型,在默认的InputFormat下,读到的value是一行文本的内容,所以value的类型是String,对应 Hadoop 中的 Text
  • KEYOUT:用户自定义逻辑方法返回数据中key的类型,由用户业务逻辑决定,在此程序中,我们输出的key是封装并实现了自定义排序的流量信息类 FlowBeanSort
  • VALUEOUT:用户自定义逻辑方法返回数据中value的类型,由用户业务逻辑决定,在此程序中,我们输出的 value 是手机号,所以是String,对应 Hadoop 中的 Text

3.2:Reduce 端程序编写

  分析:这里的 reduce()方法只需要实现将 Map 端输出的 key-value 调换后输出即可。

public class FlowSumSortReducer extends Reducer<FlowBeanSort, Text, Text, FlowBeanSort> {
	/*
	 * <FlowBeanSort,电话号> ===> <电话号,FlowBeanSort>
	 */
	@Override
	protected void reduce(FlowBeanSort key, Iterable<Text> values,
			Reducer<FlowBeanSort, Text, Text, FlowBeanSort>.Context context) throws IOException, InterruptedException {
		// 遍历集合
		for (Text tele : values) {
			// 将手机号作为key,将封装好的流量信息作为value,作为最终的输出结果
			context.write(new Text(tele), key);
		}
	}
}
  • KEYIN:对应 Mapper 端输出的 KEYOUT,即封装的流量信息类 FlowBeanSort
  • VALUEIN:对应 Mapper 端输出的 VALUEOUT,即手机号,所以是 String,对应 Hadoop 中的 Text
  • KEYOUT:用户自定义逻辑方法返回数据中key的类型,由用户业务逻辑决定,在此程序中,我们输出的key是手机号,所以是String,对应 Hadoop 中的 Text
  • VALUEOUT:用户自定义逻辑方法返回数据中value的类型,由用户业务逻辑决定,在此程序中,我们输出的value是封装好的实现了自定义排序的流量信息类,所以是 FlowBeanSort

3.3:Driver 端程序编写

  Driver 端为该 FlowSumSortDemo 程序运行的入口,相当于 YARN 集群(分配运算资源)的客户端,需要创建一个 Job 类对象来管理 MapReduce 程序运行时需要的相关运行参数,最后将该 Job 类对象提交给 YARN。

  Job对象指定作业执行规范,我们可以用它来控制整个作业的运行。接下来,我们来看一下作业从提交到执行的整个过程。

  FlowSumSortDemo.java 的完整代码如下所示:

public class FlowSumSortDemo {
	/**
	 * 得出上题结果的基础之上再加一个需求:将统计结果按照总流量倒序排序;
	 */
	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		// (1)获取配置信息类
		Configuration conf = new Configuration();
		// 指定mapreduce程序运行的hdfs的相关运行参数
		conf.set("fs.defaultFS", "hdfs://localhost:9000");

		// (2)新建一个Job对象
		Job job = Job.getInstance(conf);

		// (3)将 job 所用到的那些类(class)文件,打成jar包 (打成jar包在集群运行必须写)
		job.setJarByClass(FlowSumSortDemo.class);

		// (4)指定 Mapper 类和 Reducer 类
		job.setMapperClass(FlowSumSortMapper.class);
		job.setReducerClass(FlowSumSortReducer.class);

		// (5)指定 MapTask 的输出key-value类型
		job.setMapOutputKeyClass(FlowBeanSort.class);
		job.setMapOutputValueClass(Text.class);

		// (6)指定 ReduceTask 的输出key-value类型
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(FlowBeanSort.class);

		// (7)指定该 mapreduce 程序数据的输入和输出路径
		Path inPath = new Path("/flow/output_sum");
		Path outPath = new Path("/flow/output_sort");

		// 获取fs对象
		FileSystem fs = FileSystem.get(conf);
		if (fs.exists(outPath)) {
			fs.delete(outPath, true);
		}

		FileInputFormat.setInputPaths(job, inPath);
		FileOutputFormat.setOutputPath(job, outPath);

		// (8)最后给YARN来运行,等着集群运行完成返回反馈信息,客户端退出
		boolean waitForCompletion = job.waitForCompletion(true);
		System.exit(waitForCompletion ? 0 : 1);
	}
}

  执行结果如下所示:

13502468823	101663100	1529437140	1631100240
13925057413	153263880	668647980	821911860
13726238888	34386660	342078660	376465320
13726230503	34386660	342078660	376465320
18320173382	132099660	33430320	165529980
13560439658	28191240	81663120	109854360
13660577991	96465600	9563400	106029000
15013685858	50713740	49036680	99750420
13922314466	41690880	51559200	93250080
15920133257	43742160	40692960	84435120
84138413	57047760	19847520	76895280
13602846565	26860680	40332600	67193280
18211575961	21164220	29189160	50353380
15989002119	26860680	2494800	29355480
13560436666	15467760	13222440	28690200
13926435656	1829520	20956320	22785840
13480253104	2494800	2494800	4989600
13826544101	3659040	0	3659040
13926251106	3326400	0	3326400
13760778710	1663200	1663200	3326400
13719199419	3326400	0	3326400

猜你喜欢

转载自blog.csdn.net/c_lanxiaofang/article/details/107837031
8.4
今日推荐