文章目录


排序

▶ 排序的定义

排序是MapReduce框架中最重要的操作之一

  • MapTask 和 ReduceTask 均会对数据按照 key 进行排序​。该操作属于 Hadooop 的默认行为。任何应用程序中的数据均会被排序,而不管逻辑上是否需要。
  • 默认排序是按照字典顺序排序,且实现该排序的方法时快速排序。
  • 对于 MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。
  • 对于 ReduceTask,它从每个 MapTask上远程拷贝相应的据文件,如果文件大小超过一定阈值,则溢写到盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行次合并后将数据溢写到磁盘上。当所有数据拷贝完。最后, ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。

返回顶部


▶ 排序的分类

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

(2)全排序
​​​最终输出结果只有一个文件,且文件内部有序​​​。实现方式是只设置一个 Reducetask。但该方法在
处理大型文件时效率极低,因为一合机器处理所有文件,完全丧失了MapReduce的并行架构

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

(4)二次排序
在自定义排序过程中,如果​​​compareTo中的判条件​​为两个,即为二次排序。首先按照第一个条件先排一次,再按照第二个条件进行排序

返回顶部


☠ 自定义排序 ---- 实现WritableComparable接口

原理分析

bean对象做为key传输,需要​实现WritableComparable接口​​重写compareTo方法​,就可以实现自定义排序。

@Override
public int compareTo(FlowBean o) {
int result;
// 按照总流量大小,倒序排列
if (sumFlow > bean.getSumFlow()) {
result = -1;
}else if (sumFlow < bean.getSumFlow()) {
result = 1;
}else {
result = 0;
}
return result;
}

返回顶部


☠ WritableComparable排序案例(全排序)

最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个 Reducetask,由于默认ReduceTask数是1,也可以不设置。但该方法在处理大型文件时效率极低,因为一合机器处理所有文件,完全丧失了MapReduce的并行架构。

▪ 案例

对之前统计的流量总值进行排序

【MapReduce】MR 框架原理 之 排序_hadoop

【MapReduce】MR 框架原理 之 排序_hadoop_02


返回顶部


需求分析

【MapReduce】MR 框架原理 之 排序_apache_03

返回顶部


代码实现
Bean类
在这里插入代码片package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.WritableComparable;

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

public class FB implements WritableComparable<FB>{
// 变量
private long upFlow;
private long downFlow;
private long sumFlow;
/**
* 空参构造
*/
public FB() {}
/**
* 含参构造
* @param upFlow
* @param downFlow
*/
public FB(long upFlow, long downFlow) {
this.upFlow = upFlow;
this.downFlow = downFlow;
sumFlow = upFlow+ downFlow;
}

/**
* 重写compareTo方法
* @param o
* @return
*/
@Override
public int compareTo(FB bean) {
int result;
// 核心比较
if (sumFlow > bean.getSumFlow()){
result = -1;
}else if(sumFlow < bean.getSumFlow()){
result = 1;
}else {
result = 0;
}
return result;
}

/**
* 序列化
* @param dataOutput
* @throws IOException
*/
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}

/**
* 反序列化
* @param dataInput
* @throws IOException
*/
@Override
public void readFields(DataInput dataInput) throws IOException {
upFlow = dataInput.readLong();
downFlow = dataInput.readLong();
sumFlow = dataInput.readLong();
}

/**
* 重写toString方法
* @return
*/
@Override
public String toString() {
return upFlow+"\t"+downFlow+"\t"+sumFlow;
}
/**
* 求和方法
*/
public void set(long upFlow1,long downFlow1){
upFlow = upFlow1;
downFlow = downFlow1;
sumFlow = upFlow + downFlow;
}
/**
* set、get方法
* @return
*/
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;
}
}

与序列化统计总流量案例相比较,排序的本案例是在其结果集基础上进行的。所以输入文件就是上次的统计结果。本案例中要求对总流量进行排序,所以依然采用封装序列化并且实现compareTo方法的时候针对封装的对象中的总流量进行

/**
* 重写compareTo方法
* @param o
* @return
*/
@Override
public int compareTo(FB bean) {
int result;
// 核心比较
if (sumFlow > bean.getSumFlow()){
result = -1;
}else if(sumFlow < bean.getSumFlow()){
result = 1;
}else {
result = 0;
}
return result;
}

返回顶部


Mapper阶段

这里会有所改变。因为 MapTask 和 ReduceTask 均会对数据按照 key 进行排序,而我们要排的是总流量,在Bean对象中,所以这里要将Bean对象设为key,手机号码设为value。之前将手机号设为key是因为同一个手机号的流量记录可能有多条,按照手机号统计避免重复未合并完全。所以在写泛型的时候,输出部分应该是​FB,Text​

package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;

public class FB_M extends Mapper <LongWritable, Text,FB,Text> {

FB k = new FB();
Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1.读取一整行数据
// 手机号 上行量 下行量 总流量
// 13509468723 7335 110349 117684
String line = value.toString();
// 2.拆分
String[] fields = line.split("\t");
// 3.封装对象
String phone = fields[0];
long upFlow = Long.parseLong(fields[1]);
long downFlow = Long.parseLong(fields[2]);
long sumFlow = Long.parseLong(fields[3]);
k.setUpFlow(upFlow);
k.setDownFlow(downFlow);
k.setSumFlow(sumFlow);
v.set(phone);
// 3.写入
// k -> Bean ; v -> Text
context.write(k,v);
}
}

返回顶部


Reducer阶段

Reducer阶段并没有具体的操作,排序阶段已经在读取数据的时候(shuffle过程)利用compareTo方法完成了。但是这里为了最后的输出形式符合正常需求,在最后输出的时候手机号在前,流量信息在后

package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;

public class FB_R extends Reducer<FB, Text,Text,FB> {
@Override
protected void reduce(FB key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text value:values){
// 写入
// values是输入的不同手机号,key是对应的Bean对象,存储了流量信息
context.write(value,key);
}
}
}

返回顶部


Driver阶段

文件输入的路径就是之前流量统计值后的输出文件路径。

package 第三章_MR框架原理.排序;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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 FB_D {
public static void main(String[] args) {
Job job = null;
Configuration conf = new Configuration();
try {
// 获取job对象
job = Job.getInstance(conf);
// 配置
job.setMapperClass(FB_M.class);
job.setReducerClass(FB_R.class);
job.setJarByClass(FB_D.class);
job.setMapOutputKeyClass(FB.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FB.class);
// 设置输入输出路径
FileInputFormat.setInputPaths(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第二章_Hadoop序列化\\output"));
FileOutputFormat.setOutputPath(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第三章_MR框架原理\\排序\\全排output"));
// 提交job
boolean result = job.waitForCompletion(true);
System.exit(result?0:1);
} catch (Exception e ){
e.printStackTrace();
}
}
}

【MapReduce】MR 框架原理 之 排序_mapreduce_04

返回顶部


☠ WritableComparable排序案例(分区、排序)

▪ 案例

对排序好的文件进行分区、排序,按照手机号将数据分到不同的区中,并进行排序输出文件。


需求分析
  • 基于前一个需求,增加自定义分区类,分区按照省份手机号设置。
  • 【MapReduce】MR 框架原理 之 排序_mapreduce_05

  • 返回顶部

代码实现
PhonePartitioner分区类
package 第三章_MR框架原理.排序;

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

public class PhonePartitioner extends Partitioner<FBP,Text> {

@Override
public int getPartition(FBP fbp, Text text, int numPartitions) {
// key 是手机号
// value 是户主信息
// 1.获取手机号前三位
String phoneNum = text.toString().substring(0,3);
// 2.定义分区数 注意:分区数必须从0开始
int partition = 4;
if ("136".equals(phoneNum)){
partition = 0;
} else if ("137".equals(phoneNum)){
partition = 1;
} else if ("138".equals(phoneNum)){
partition = 2;
}else if ("139".equals(phoneNum)){
partition = 3;
} else {
partition = 4;
}
return partition;
}
}

返回顶部


Bean类
package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

public class FBP implements WritableComparable<FBP> {

private long upFlow;
private long downFlow;
private long sumFlow;
public FBP() {
}
@Override
public int compareTo(FBP bean) {
int result = 0;
if (sumFlow>bean.getSumFlow()){
result = -1;
}else if (upFlow<bean.getSumFlow()){
result = 1;
}else {
result = 0;
}
return result;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeLong(upFlow);
dataOutput.writeLong(downFlow);
dataOutput.writeLong(sumFlow);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
upFlow = dataInput.readLong();
downFlow = dataInput.readLong();
sumFlow = dataInput.readLong();
}
@Override
public String toString() {
return upFlow+"\t"+downFlow+"\t"+sumFlow;
}
public void set(long upFlow1, long downFlow1){
upFlow = upFlow1;
downFlow = downFlow1;
sumFlow = upFlow + downFlow;
}
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;
}
}

返回顶部


Mapper阶段
package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;

public class FBP_M extends Mapper<LongWritable, Text,FBP,Text> {
FBP k = new FBP();
Text v = new Text();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 13509468723 7335 110349 117684
// 13975057813 11058 48243 59301
// 1.读取一行数据
String line = value.toString();
// 2.分割
String[] fields = line.split("\t");
// 3.封装对象
String phone = fields[0];
long upFlow = Long.parseLong(fields[1]);
long downFlow = Long.parseLong(fields[2]);
long sumFlow = Long.parseLong(fields[3]);
k.setUpFlow(upFlow);
k.setDownFlow(downFlow);
k.setSumFlow(sumFlow);
v.set(phone);
// 4.写入
context.write(k,v);
}
}

返回顶部


Reducer阶段
package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;

public class FBP_R extends Reducer<FBP, Text,Text,FBP> {
@Override
protected void reduce(FBP key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text value:values){
// 写出
context.write(value,key);
}
}
}

返回顶部


Driver阶段
package 第三章_MR框架原理.排序;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
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 FBP_D {
public static void main(String[] args) {
Job job = null;
Configuration conf = new Configuration();
try {
// 获取job对象
job = Job.getInstance(conf);
// 配置
job.setMapperClass(FBP_M.class);
job.setReducerClass(FBP_R.class);
job.setJarByClass(FBP_D.class);
job.setMapOutputKeyClass(FBP.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(FBP.class);
// 设置分区信息
job.setPartitionerClass(PhonePartitioner.class);
// 同时指定相应数量的reduce task
job.setNumReduceTasks(5);
// 设置输入输出路径
FileInputFormat.setInputPaths(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第三章_MR框架原理\\排序\\全排output"));
FileOutputFormat.setOutputPath(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第三章_MR框架原理\\排序\\分区排output"));
// 提交job
boolean result = job.waitForCompletion(true);
System.exit(result?0:1);
} catch (Exception e ){
e.printStackTrace();
}
}
}

【MapReduce】MR 框架原理 之 排序_hadoop_06

返回顶部


☠ WritableComparator排序案例(辅助排序—GroupingComparator、二次排序)



GroupingComparator分组(辅助排序)

  • 对Reduce阶段的数据根据某一个或几个字段进行分组。
  • 分组排序步骤:
  • (1)自定义类继承WritableComparator
  • (2)重写compare()方法
@Override
public int compare(WritableComparable a, WritableComparable b) {
// 比较的业务逻辑
return result;
}

          ▪ (3)创建一个构造将比较对象的类传给父类

protected OrderGroupingComparator() {
super(OrderBean.class, true);
}


▪ 案例

现在需要求出每一个订单中最贵的商品。

(1)输入数据

【MapReduce】MR 框架原理 之 排序_mapreduce_07


(2)期望输出数据

1 222.8
2 722.4
3 232.8

需求分析

(1)利用“订单id和成交金额​作为key​,可以​将Map阶段读取到的所有订单数据按照id升序排序,如果id相同再按照金额降序排序​,发送到Reduce。

2)在Reduce端利用groupingComparator将订单id相同的kv聚合成组,然后​取第一个​即是该订单中最贵商品。

【MapReduce】MR 框架原理 之 排序_hadoop_08

返回顶部


代码实现
Bean类
  • 构建OB对象,实现序列化、反序列化,自定义排序(二次排序)
package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.WritableComparable;

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

public class OB implements WritableComparable<OB> {
// 变量声明,只封装需要的信息
private int order_id;
private double price;

// 空参构造
public OB() {
}

// 重写compareTo --- 二次排序
@Override
public int compareTo(OB bean) {
int result;
// 先按照订单id升序排序
if (order_id > bean.getOrder_id()){
result = 1; // 升序1
} else if (order_id < bean.getOrder_id()){
result = -1;
} else {
// 若相同,再按照价格降序排序
if (price>bean.getPrice()){
result = -1;
}else if (price<bean.getPrice()){
result = 1;
}else {
result = 0;
}
}
return result;
}

// 序列化
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeInt(order_id);
dataOutput.writeDouble(price);
}

// 反序列化
@Override
public void readFields(DataInput dataInput) throws IOException {
order_id = dataInput.readInt();
price = dataInput.readDouble();
}

// 重写toString
@Override
public String toString() {
return order_id + "\t" + price;
}

// set、get
public int getOrder_id() {
return order_id;
}

public void setOrder_id(int order_id) {
this.order_id = order_id;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}
}

返回顶部


Mapper阶段
  • 将order_id和price都封装到OB对象中
package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;

public class OB_M extends Mapper<LongWritable, Text,OB, NullWritable> {
OB k = new OB();
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 0000001 Pdt_01 222.8
// 1.读取一行数据
String line = value.toString();
// 2.拆分
String[] values = line.split(" ");
// 3.封装bean对象
int order_id = Integer.parseInt(values[0]);
double price = Double.parseDouble(values[2]);
k.setOrder_id(order_id);
k.setPrice(price);
// 4.写出
context.write(k,NullWritable.get());
}
}

返回顶部


Reducer阶段
package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;

public class OB_R extends Reducer<OB, NullWritable,OB,NullWritable> {
@Override
protected void reduce(OB key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(key,NullWritable.get());
}
}

返回顶部


OrderSortGroupingComparator辅助排序
package 第三章_MR框架原理.排序;

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

public class OrderSortGroupingComparator extends WritableComparator {
// 构造方法
public OrderSortGroupingComparator() {
super(OB.class,true);
}

// 重写compare
@Override
public int compare(WritableComparable a, WritableComparable b) {
// 要求只要id相同,就认为是相同的key
int result;
OB aBean = (OB) a;
OB bBean = (OB) b;
if (aBean.getOrder_id() > bBean.getOrder_id()){
result = 1;
}else if (aBean.getOrder_id() < bBean.getOrder_id()){
result = -1;
}else {
result = 0;
}
return result;
}
}

返回顶部


Driver阶段
package 第三章_MR框架原理.排序;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class OB_D {
public static void main(String[] args) {
Job job = null;
Configuration conf = new Configuration();
try {
// 1.获取job对象
job = Job.getInstance(conf);
// 2.配置
job.setMapperClass(OB_M.class);
job.setReducerClass(OB_R.class);
job.setJarByClass(OB_D.class);
job.setMapOutputKeyClass(OB.class);
job.setMapOutputValueClass(NullWritable.class);
job.setOutputKeyClass(OB.class);
job.setOutputValueClass(NullWritable.class);
// 3.设置分组比较
job.setGroupingComparatorClass(OrderSortGroupingComparator.class);
// 4.设置输入输出路径
FileInputFormat.setInputPaths(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第三章_MR框架原理\\排序\\GroupingComparator.txt"));
FileOutputFormat.setOutputPath(job,new Path("G:\\Projects\\IdeaProject-C\\MapReduce\\src\\main\\java\\第三章_MR框架原理\\排序\\辅助排序output1"));
// 5.提交job
boolean result = job.waitForCompletion(true);
System.exit(result ? 0 : 1 );
} catch (Exception e){
e.printStackTrace();
}
}
}

【MapReduce】MR 框架原理 之 排序_hadoop_09


补充:

  • 如果要输出价格排名前两名的订单,只需要空值Redeucer阶段的输出次数
package 第三章_MR框架原理.排序;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;

public class OB_R extends Reducer<OB, NullWritable,OB,NullWritable> {
@Override
protected void reduce(OB key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
int i = 1;
for (NullWritable value:values){
if (i>2){
break;
}
context.write(key,NullWritable.get());
i++;
}
}
}

返回顶部