利用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. 并发容器之ConcurrentLinkedQueue

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

  2. Implement Dependent Reference Properties 实现从属引用属性 (XPO)

    In this lesson, you will learn how to implement properties whose values can depend on other properti ...

  3. 「SAP技术」SAP HU上面的'Obj.to Which HU Belongs'栏位初探

    SAP HU上面的'Obj.to Which HU Belongs'栏位初探 HU02,创建一个新的HU, 保存之, HU03显示这个HU 189141203942, 其'obj.to Which H ...

  4. 更改组织属性-以更改maxrecordsforexporttoexcel为例

    关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复232或者20161101可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyong. ...

  5. Kafka学习(一)

    官网 kafka.apache.org 集群部署 消息中间键 --> 分布式流式平台 Kafka Streaming Flume: 1个进程包含三个角色 source channle sink ...

  6. Scrapy中的Request和日志分析

    Scrapy.http.Request 自动去重,根据url的哈希值,进行去重 属性 meta(dict)  在不同的请求之间传递数据,dict priority(int)  此请求的优先级(默认为0 ...

  7. 从傅里叶变换(FFT)到数论变换(NTT)

    FFT可以用来计算多项式乘法,但是复数的运算中含有大量的浮点数,精度较低.对于只有整数参与运算的多项式,有时,\(\text{NTT(Number-Theoretic Transform)}\)会是更 ...

  8. WPF 精修篇 用户控件

    原文:WPF 精修篇 用户控件 增加用户控件 数据绑定还是用依赖属性 使用的事件 就委托注册一下 public delegate void ButtonClick(object b,EventArgs ...

  9. WPF (DataGridColumnHeader)实现自义定列头样式 并绑定数据

    原文:WPF (DataGridColumnHeader)实现自义定列头样式 并绑定数据 实现功能是这样的 自定义列头 样式 样式里的 数据来源于后台绑定 这篇就说头样式 和头样式数据绑定 思路 1) ...

  10. [题解向] CF#Global Round 1の题解(A $\to$ G)

    这里是总链接\(Link\). \(A\) 题意:求\(\sum_{i=1}^{k} a_i\times b^{k-i}\)的奇偶性, \(k = \Theta(n \log n)\) --其实很容易 ...