利用hadoop的map和reduce排序特性实现对数据排序取TopN条数据。

代码参考:https://github.com/asker124143222/wordcount

1、样本数据,假设是订单数据,求解按订单id排序且每个订单里价格最高前三,从高到低排序。

订单ID  商品ID   单价
0000001 Pdt_01 222.8
0000002 Pdt_05 722.4
0000001 Pdt_02 33.8
0000003 Pdt_06 232.8
0000003 Pdt_02 33.8
0000002 Pdt_03 522.8
0000002 Pdt_04 122.4
0000001 Pdt_01 122.8
0000002 Pdt_05 522.4
0000003 Pdt_02 133.8
0000002 Pdt_03 222.8
0000002 Pdt_04 222.4
0000001 Pdt_01 322.8
0000002 Pdt_05 322.4

2、求解思路

1.将订单封装成bean,以bean对象作为map的key,这样才能利用hadoop的key自动排序特性。
2.实现WritableComparable接口,bean以id升序,价格降序实现比较接口,这样数据在map后进入shuffle阶段会实现自定义规则自排序。
3、reduce阶段,如果不做任何处理数据将呈现将按订单升序,价格降序。但是订单id相同,价格不同的订单将不能使用同一个reduce函数,也不能求解topN(是指利用key排序特性的topN,否则实现topN的手段还有很多)。
4、使用reduce阶段分组特性接口WritableComparator,在reduce归并前,将对数据进行分组,以决定什么样的数据进入同一分组里,即同一个reduce里。
5、在实现WritableComparator的类中,以bean为基础,我们将订单id作为比较项忽略价格因素,实现同一id,进入同一个分组,价格从高到低已经在bean里排序实现,shuffle阶段也遵循了这个原则,所以在reduce阶段不考虑价格排序问题。
6、最后一个难点,通过给reduce的数据分组,传递到reduce里key就是同一个订单id最大价格的订单,一般情况下,我们从map阶段传递过来的values都是null,reduce阶段也是一个null值的迭代器,如何求topN呢,这个时候实现的只是max。
7、这个才是最后一个步骤,null的迭代器里其实存了分组里每一个值,包括key和value(虽然value是null),而这个迭代器里key和reduce的key是共享地址,也就是指向同一个变量,
当我们使用迭代器滚动取值的时候,reduce里的key的值也被迭代里的key值重新赋值(它们指向同一内存地址),所以在执行迭代过程中,我们就可以轻松求解TopN。

3、code

3.1 OrderBean

public class OrderBean implements WritableComparable<OrderBean> {

    private int order_id;
private double price; public OrderBean() {
} public OrderBean(int order_id, double price) {
this.order_id = order_id;
this.price = price;
} @Override
public int compareTo(OrderBean o) {
//订单id升序,价格降序
if(this.getOrder_id()>o.getOrder_id()){
return 1;
}else if (this.getOrder_id()<o.getOrder_id()){
return -1;
}
else{
if(this.getPrice()>o.getPrice()){
return -1;
}else if(this.getPrice()<o.getPrice()){
return 1;
}else{
return 0;
}
}
} @Override
public void write(DataOutput out) throws IOException {
out.writeInt(order_id);
out.writeDouble(price);
} @Override
public void readFields(DataInput in) throws IOException {
this.order_id = in.readInt();
this.price = in.readDouble();
} 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;
} @Override
public String toString() {
return "order_id=" + order_id +
", price=" + price;
}
}

3.2 mapper

public class OrderMapper extends Mapper<LongWritable, Text,OrderBean, NullWritable> {
OrderBean k = new OrderBean(); @Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1 获取一行
String line = value.toString(); // 2 分割
String[] fields = line.split("\\s+"); // 3 封装对象
k.setOrder_id(Integer.parseInt(fields[0]));
k.setPrice(Double.parseDouble(fields[2])); // 4 写出,value值是null
context.write(k, NullWritable.get()); }
}

3.3 reducer的分组规则

/**
* @Author: xu.dm
* @Date: 2019/8/30 16:15
* @Version: 1.0
* @Description: reducer数据分组,在数据从map阶段送到reducer后,在归并执行前,重新进行分组
* 通过这种方式,重新调整数据进入reducer的key值
*
* 本例中,map送过来的key是OrderBean,也是按Orderbean排序(id升序,价格降序),
* 数据送到reduce后,如果不分组,那么相同id不同价格的数据被认为是不同的key,
* 经过自定义分组(继承WritableComparator),只使用id作为分组的条件,
* reduce在归并前key的数据只按id判断,价格被忽略,
* 那么:{1,300}和{1,200}这种数据就会被认为是相同的key,即key=1,其他忽略,所以最终输出的时候,价格只保留最高。
**/
public class OrderSortGroupingComparator extends WritableComparator { protected OrderSortGroupingComparator() {
super(OrderBean.class, true);
} @Override
public int compare(WritableComparable a, WritableComparable b) {
OrderBean aBean = (OrderBean)a;
OrderBean bBean = (OrderBean)b;
if(aBean.getOrder_id()>bBean.getOrder_id()){
return 1;
}else if(aBean.getOrder_id()<bBean.getOrder_id()){
return -1;
}else {
return 0;
}
}
}

3.4 reducer

/**
* @Author: xu.dm
* @Date: 2019/8/30 16:21
* @Version: 1.0
* @Description: 如果不执行Iterable<NullWritable> values迭代,直接取key
* 那么key根据分组只保留从map->shuffle->reduce流程里第一个排序值,如果key是一个bean对象(即复合键),key就是排序输出的第一个对象。
*
* 如果执行Iterable<NullWritable> values迭代,那么迭代器滚动数据过程中,会依次对OrderBean key赋值,
* 原理是Iterable<NullWritable> values里也存了key值,滚动中key被取出,而迭代器里key和reduce里key公用内存地址(复用)
* 所以迭代器滚动过程,对key和value都进行了赋值
* 可以用 OrderBean mykey = new OrderBean(key.getOrder_id(),key.getPrice());来测试,mykey是不会跟着迭代器滚动的。
* 通过这个特性,可以实现排序数据取TopN
**/
public class OrderReducer extends Reducer<OrderBean, NullWritable,OrderBean,NullWritable> {
@Override
protected void reduce(OrderBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {
// OrderBean mykey = new OrderBean(key.getOrder_id(),key.getPrice());
//实现topN数据输出
int topN = 3;
for (NullWritable value : values) {
System.out.println(key.hashCode());
context.write(key,NullWritable.get());
topN--;
if(topN<=0)break;
} }
}

3.5 driver

/**
* @Author: xu.dm
* @Date: 2019/8/30 16:25
* @Version: 1.0
* @Description: 求解每个订单最大单价订单以及取TopN。
**/
public class OrderDriver {
public static void main(String[] args) throws Exception, IOException {
if(args.length!=2)
{
System.err.println("使用格式:FlowSortedDriver <input path> <output path>");
System.exit(-1);
} // 1 获取配置信息
Configuration conf = new Configuration();
Job job = Job.getInstance(conf); // 2 设置jar包加载路径
job.setJarByClass(OrderDriver.class); // 3 加载map/reduce类
job.setMapperClass(OrderMapper.class);
job.setReducerClass(OrderReducer.class); // 4 设置map输出数据key和value类型
job.setMapOutputKeyClass(OrderBean.class);
job.setMapOutputValueClass(NullWritable.class); // 5 设置最终输出数据的key和value类型
job.setOutputKeyClass(OrderBean.class);
job.setOutputValueClass(NullWritable.class); // 6 设置输入数据和输出数据路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1])); // 8 设置reduce端的分组
job.setGroupingComparatorClass(OrderSortGroupingComparator.class); // 7 提交
//测试环境下,可以先删除目标目录
Path outPath = new Path(args[1]);
FileSystem fs = FileSystem.get(conf);
if(fs.exists(outPath)){
fs.delete(outPath,true);
} long startTime = System.currentTimeMillis();
boolean result = job.waitForCompletion(true); long endTime = System.currentTimeMillis();
long timeSpan = endTime - startTime;
System.out.println("运行耗时:"+timeSpan+"毫秒。"); System.exit( result ? 0 : 1);
} }

3.6 刷出结果

order_id=1, price=322.8
order_id=1, price=222.8
order_id=1, price=122.8
order_id=2, price=722.4
order_id=2, price=522.8
order_id=2, price=522.4
order_id=3, price=232.8
order_id=3, price=133.8
order_id=3, price=33.8
 

hadoop mapreduce求解有序TopN的更多相关文章

  1. hadoop mapreduce求解有序TopN(高效模式)

    1.在map阶段对数据先求解改分片的topN,到reduce阶段再合并求解一次,求解过程利用TreeMap的排序特性,不用自己写算法. 2.样板数据,类似如下 1 13682846555 192.16 ...

  2. Hadoop Mapreduce分区、分组、二次排序过程详解[转]

    原文地址:Hadoop Mapreduce分区.分组.二次排序过程详解[转]作者: 徐海蛟 教学用途 1.MapReduce中数据流动   (1)最简单的过程:  map - reduce   (2) ...

  3. 从分治算法到 Hadoop MapReduce

    从分治算法说起 要说 Hadoop MapReduce 就不得不说分治算法,而分治算法其实说白了,就是四个字 分而治之 .其实就是将一个复杂的问题分解成多组相同或类似的子问题,对这些子问题再分,然后再 ...

  4. 三种方法实现Hadoop(MapReduce)全局排序(1)

    我们可能会有些需求要求MapReduce的输出全局有序,这里说的有序是指Key全局有序.但是我们知道,MapReduce默认只是保证同一个分区内的Key是有序的,但是不保证全局有序.基于此,本文提供三 ...

  5. 分别使用Hadoop和Spark实现TopN(1)——唯一键

    0.简介 TopN算法是一个经典的算法,由于每个map都只是实现了本地的TopN算法,而假设map有M个,在归约的阶段只有M x N个,这个结果是可以接受的并不会造成性能瓶颈. 这个TopN算法在ma ...

  6. Hadoop MapReduce 一文详解MapReduce及工作机制

    @ 目录 前言-MR概述 1.Hadoop MapReduce设计思想及优缺点 设计思想 优点: 缺点: 2. Hadoop MapReduce核心思想 3.MapReduce工作机制 剖析MapRe ...

  7. hadoop MapReduce运营商案例关于用户基站停留数据统计

    注 如果需要文件和代码的话可评论区留言邮箱,我给你发源代码 本文来自博客园,作者:Arway,转载请注明原文链接:https://www.cnblogs.com/cenjw/p/hadoop-mapR ...

  8. Hadoop - MapReduce 过程

    Hadoop - MapReduce 一.MapReduce设计理念 map--->映射 reduce--->归纳 mapreduce必须构建在hdfs之上的一种大数据离线计算框架 在线: ...

  9. Hadoop MapReduce执行过程详解(带hadoop例子)

    https://my.oschina.net/itblog/blog/275294 摘要: 本文通过一个例子,详细介绍Hadoop 的 MapReduce过程. 分析MapReduce执行过程 Map ...

随机推荐

  1. Python语法易错点

    列表.数组赋值 a = [1,6] b = a * 2 b[0] = -9999 print(a) print(b) [1, 6] [-9999, 6, 1, 6] a = [1,6] b = a b ...

  2. Oracle merge into的优势

    简介 Oracle merge into命令,顾名思义就是“有则更新,无则插入”,这个也是merge into 命令的核心思想,在实际开发过程中,我们会经常遇到这种通过两表互相关联匹配更新其中一个表的 ...

  3. 设计院老师良心汇总:值得牢记的15个CAD基础技巧,能帮大忙

    哈喽!你们的CAD魔鬼(老师)来喽! 良心CAD技巧汇总,设计院师傅精心汇总,值得你牢记的15个CAD基础技巧,满满的都是干货,日常最常见的问题以及解决方法这里都汇总给你,给你高效的绘图体验,关键时刻 ...

  4. cookie --中间件

    Cookie简介 cookie是服务器存储在用户计算机中的变量,可以让我们用同一个浏览器访问同一个域名的时共享数据. HTTP是一种无状态协议,简单来说,当你从一个页面,然后跳转到同站点的另一个页面时 ...

  5. 【ASP.NET Core】AddMvc和AddMvcCore的区别

    AddMvcCore() method only adds the core MVC services. AddMvc() method adds all the required MVC servi ...

  6. Dojo.declare使用方法详解

    ArcGIS API for JavaScript是基于dojo开发的一套API,在实际生产中,我们需要再根据自己的需求实现自定义的功能,最后抽象成接口给前端调用. 我们使用dojo的declare来 ...

  7. MySQL 优化 (二)

    参数优化 Max_connections (1)简介 Mysql的最大连接数,如果服务器的并发请求量比较大,可以调高这个值,如果连接数越来越多,mysql会为每个连接提供单独的缓冲区,就会开销的越多的 ...

  8. PyCharm批量修改变量名

    方法和 PyCharm重命名文件时更改引用的地方相同

  9. 游戏AI之A*寻路算法(3)

    前言:寻路是游戏比较重要的一个组成部分.因为不仅AI还有很多地方(例如RTS游戏里操控人物点到地图某个点,然后人物自动寻路走过去)都需要用到自动寻路的功能. 本文将介绍一个经常被使用且效率理想的寻路方 ...

  10. 10. java 匿名对象说明

    一.匿名对象 public class Demo{ public static void main(String[] args){ Person one = new Person(); one.nam ...