WritableComparable介绍
在前面的几个列子的结构文件中我们可以看到排序结果默认按照英文字母进行排序
这是因为在MapReduce中,会自动的对放在键的位置上的元素进行排序,因此要求放在键的位置上的元素对应的类必须实现Comparable。默认排序是按照字典顺序排序,且实现该排序的方法是快速排序。
对于MapTask,它会将处理的结果暂时放到环形缓冲区中,当环形缓冲区使用率达到一定阈值后,再对缓冲区中的数据进行一次快速排序,并将这些有序数据溢写到磁盘上,而当数据处理完毕后,它会对磁盘上所有文件进行归并排序。
对于ReduceTask,它从每个MapTask上远程拷贝相应的数据文件,如果文件大小超过一定阈值,则溢写磁盘上,否则存储在内存中。如果磁盘上文件数目达到一定阈值,则进行一次归并排序以生成一个更大文件;如果内存中文件大小或者数目超过一定阈值,则进行一次合并后将数据溢写到磁盘上。当所有数据拷贝完毕后,ReduceTask统一对内存和磁盘上的所有数据进行一次归并排序。
排序分类
(1)部分排序
MapReduce根据输入记录的键对数据集排序。保证输出的每个文件内部有序。
(2)全排序
最终输出结果只有一个文件,且文件内部有序。实现方式是只设置一个ReduceTask。但该方法在处理大型文件时效率极低,因为一台机器处理所有文件,完全丧失了MapReduce所提供的并行架构。
(3)辅助排序:(GroupingComparator分组)
在Reduce端对key进行分组。应用于:在接收的key为bean对象时,想让一个或几个字段相同(全部字段比较不相同)的key进入到同一个reduce方法时,可以采用分组排序。
(4)二次排序
在自定义排序过程中,如果compareTo中的判断条件为两个即为二次排序。
排序代码实现
考虑到MapReduce要求被传输的数据能够被序列化,因此放在键的位置上的元素对应的类要考虑实现 - WritableComparable接口,简单点说就是bean 对象做为 key 传输,需要实现 WritableComparable 接口重写 compareTo 方法,就可以实现排序。
案例按照,第三个字段进行排序,数据peopleinfoorder.txt如下
1880349000110,4321,7000
1880349000111,4121,6000
1880349000112,4021,3000
代码
package order;
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;
// 用于完成Map阶段
// 再MapReduce中,要求被处理的数据能够被序列化
// MApReduce提供了一套单独的序列化机制
// KEYIN-输入的键的类型。如果不指定,那么默认情况下,表示行的字节偏移量
// VALUEIN-输入值得类型。如果不指定,那么默认情况下,表示的读取到的一行数据
// KEYOUT-输出的键的类型。当前案例中,输出的键表示的是PeopleInfo对象
// VALUEOUT-输出的值的类型。当前案例,输出的值为null,因为KEYOUT里边已经包含了
public class FlowMapper extends Mapper<LongWritable, Text, PeopleInfo, NullWritable> {
// 覆盖map方法,将处理逻辑写到这个方法中
// key:键。表示的是行的字节偏移量
// value:值。表示读取到的一行数据
// context:配置参数
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] array = value.toString().split(",");
PeopleInfo p = new PeopleInfo();
p.setPhone(array[0]);
p.setUpFlow(Integer.parseInt(array[1]));
p.setDownFlow(Integer.parseInt(array[2]));
context.write(p, NullWritable.get());
}
}
package order;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
// KEYIN、VALUEIN输入的键的类型。
// Reducer的数据从Mapper来的
// 所以Mapper的输出就是Reducer的输入
// KEYOUT、VALUEOUT-输出的值的类型。当前案例中,要排序输出每一个手机号对应的上下行流量
public class FlowReducer extends Reducer<PeopleInfo, NullWritable, Text, Text> {
@Override
protected void reduce(PeopleInfo key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(new Text(key.getPhone()), new Text(key.getUpFlow() + "\t" + key.getDownFlow()));
}
}
package order;
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.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import java.io.IOException;
public class JobMain {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//一、初始化Job
Configuration configuration = new Configuration();
//获取运行命令的参数,参数一:输入文件路径,参数二:输出文件路径
//如果输入路径是一个文件,那么只处理这个文件,如果指定的路径是目录,则处理这个目录下的所有文件
//输出路径只能是不存在的目录名
String [] otherArgs = new GenericOptionsParser(configuration,args).getRemainingArgs();
if(otherArgs.length < 2){
System.err.println("必须提供输入文件路径和输出文件路径");
System.exit(2);
}
Job job = Job.getInstance(configuration, "mr");
job.setJarByClass(JobMain.class);
//二、设置Job的相关信息 8个小步骤
//1、设置输入路径
job.setInputFormatClass(TextInputFormat.class);
//本地运行
//TextInputFormat.addInputPath(job,new Path("/tmp/input/mr1.txt"));
TextInputFormat.addInputPath(job,new Path(args[0]));
//2、设置Mapper类型,并设置输出键和输出值
job.setMapperClass(FlowMapper.class);
job.setMapOutputKeyClass(PeopleInfo.class);
job.setMapOutputValueClass(NullWritable.class);
//shuffle阶段,使用默认的
//3、设置Reducer类型,并设置输出键和输出值
job.setReducerClass(FlowReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//4、设置输出路径
job.setOutputFormatClass(TextOutputFormat.class);
//本地运行
//TextOutputFormat.setOutputPath(job,new Path("/tmp/output/mr"));
TextOutputFormat.setOutputPath(job,new Path(args[1]));
//三、等待完成
boolean b = job.waitForCompletion(true);
System.out.println(b==true?"MapReduce 任务执行成功!":"MapReduce 任务执行失败!");
System.exit(b ? 0 : 1);
}
}
这里注意实体代码要是写WritableComparable接口
package order;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class PeopleInfo implements WritableComparable<PeopleInfo> {
private String phone;
private int upFlow;
private int downFlow;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public int getDownFlow() {
return downFlow;
}
public void setDownFlow(int downFlow) {
this.downFlow = downFlow;
}
public int getUpFlow() {
return upFlow;
}
public void setUpFlow(int upFlow) {
this.upFlow = upFlow;
}
// 需要将有必要的属性依次序列化写出即可
@Override
public void write(DataOutput out) throws IOException {
out.writeUTF(getPhone());
out.writeInt(getUpFlow());
out.writeInt(getDownFlow());
}
@Override
public void readFields(DataInput in) throws IOException {
setPhone(in.readUTF());
setUpFlow(in.readInt());
setDownFlow(in.readInt());
}
@Override
public String toString() {
return "PeopleInfo{" +
"phone='" + phone + '\'' +
", upFlow=" + upFlow +
", downFlow=" + downFlow +
'}';
}
/**
* 按照downflow来排序
* @param o
* @return
*/
@Override
public int compareTo(PeopleInfo o) {
return this.getDownFlow() - o.getDownFlow();
}
}
打成jar包,上传到服务器上
将数据文件peopleinfoorder.txt上传到hdfs中
执行
hadoop jar /hadoopmapreduce-1.0-SNAPSHOT.jar order.JobMain /peopleinfoorder.txt /mypeopleinfoorder/
查看结果,可以看到按照第三个字段倒序排列
二次排序
MapReduce中,如果需要对多字段进行排序,那么称之为二次排序
准备数据,按月统计销售额,按照销售额大小排序 monthxiaoshou.txt,上传到服务器上
3,whm,335
1,whm,135
1,lili,236
2,jm,563
2,whm,233
1,jm,423
3,lili,623
3,jm,276
2,lili,948
代码
package twoorder;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import java.io.IOException;
public class JobMain {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
//一、初始化Job
Configuration configuration = new Configuration();
//获取运行命令的参数,参数一:输入文件路径,参数二:输出文件路径
//如果输入路径是一个文件,那么只处理这个文件,如果指定的路径是目录,则处理这个目录下的所有文件
//输出路径只能是不存在的目录名
String [] otherArgs = new GenericOptionsParser(configuration,args).getRemainingArgs();
if(otherArgs.length < 2){
System.err.println("必须提供输入文件路径和输出文件路径");
System.exit(2);
}
Job job = Job.getInstance(configuration, "mr");
job.setJarByClass(JobMain.class);
//二、设置Job的相关信息 8个小步骤
//1、设置输入路径
job.setInputFormatClass(TextInputFormat.class);
//本地运行
//TextInputFormat.addInputPath(job,new Path("/tmp/input/mr1.txt"));
TextInputFormat.addInputPath(job,new Path(args[0]));
//2、设置Mapper类型,并设置输出键和输出值
job.setMapperClass(TwoOrderMapper.class);
job.setMapOutputKeyClass(PeopleInfo.class);
job.setMapOutputValueClass(NullWritable.class);
//shuffle阶段,使用默认的
//3、设置Reducer类型,并设置输出键和输出值
job.setReducerClass(TwoOrderReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(Text.class);
//4、设置输出路径
job.setOutputFormatClass(TextOutputFormat.class);
//本地运行
//TextOutputFormat.setOutputPath(job,new Path("/tmp/output/mr"));
TextOutputFormat.setOutputPath(job,new Path(args[1]));
//三、等待完成
boolean b = job.waitForCompletion(true);
System.out.println(b==true?"MapReduce 任务执行成功!":"MapReduce 任务执行失败!");
System.exit(b ? 0 : 1);
}
}
package twoorder;
import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.yarn.webapp.hamlet2.Hamlet;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class PeopleInfo implements WritableComparable<PeopleInfo> {
private int month;
private String name = "";
private int money;
public int getMonth() {
return month;
}
@Override
public String toString() {
return "PeopleInfo{" +
"month=" + month +
", name='" + name + '\'' +
", money=" + money +
'}';
}
public void setMonth(int month) {
this.month = month;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
// 先按照月份进行升序排序;同一个月中,按照利润进行降序排序
@Override
public int compareTo(PeopleInfo o) {
int r = getMonth() - o.getMonth();
if (r == 0)
return o.getMoney() - this.getMoney();
return r;
}
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(getMonth());
out.writeUTF(getName());
out.writeInt(getMoney());
}
@Override
public void readFields(DataInput in) throws IOException {
setMonth(in.readInt());
setName(in.readUTF());
setMoney(in.readInt());
}
}
package twoorder;
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 org.apache.log4j.Logger;
import java.io.IOException;
// 用于完成Map阶段
// 再MapReduce中,要求被处理的数据能够被序列化
// MApReduce提供了一套单独的序列化机制
// KEYIN-输入的键的类型。如果不指定,那么默认情况下,表示行的字节偏移量
// VALUEIN-输入值得类型。如果不指定,那么默认情况下,表示的读取到的一行数据
// KEYOUT-输出的键的类型。当前案例中,输出的键表示的是PeopleInfo对象
// VALUEOUT-输出的值的类型。当前案例,输出的值为null,因为KEYOUT里边已经包含了
public class TwoOrderMapper extends Mapper<LongWritable, Text, PeopleInfo, NullWritable> {
private static final Logger logger = Logger.getLogger(TwoOrderMapper.class);
// 覆盖map方法,将处理逻辑写到这个方法中
// key:键。表示的是行的字节偏移量
// value:值。表示读取到的一行数据
// context:配置参数
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] array = value.toString().split(",");
PeopleInfo p = new PeopleInfo();
p.setMonth(Integer.parseInt(array[0]));
p.setName(array[1]);
p.setMoney(Integer.parseInt(array[2]));
logger.info(p);
context.write(p, NullWritable.get());
}
}
package twoorder;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
// KEYIN、VALUEIN输入的键的类型。
// Reducer的数据从Mapper来的
// 所以Mapper的输出就是Reducer的输入
// KEYOUT、VALUEOUT-输出的值的类型。当前案例中,要排序输出每一个人的金额
public class TwoOrderReducer extends Reducer<PeopleInfo, NullWritable, Text, Text> {
@Override
protected void reduce(PeopleInfo key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
context.write(new Text(key.getName()), new Text(key.getMonth() + "\t" + key.getMoney()));
}
}
打JAR包上传到服务器上执行
hadoop jar /hadoopmapreduce-1.0-SNAPSHOT.jar twoorder.JobMain /monthxiaoshou.txt /mypeopleinfotwoorder/
结果可以看到按照月份先排序,然后按照金额排序