利用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. Git错误:error:failed to push some refs to 'git@gitee.com:name/project.git'

    大家在通过本地仓库上传文件到远程仓库时,会报出 error:failed to push some refs to 'git@gitee.com:name/project.git' 的错误. 解决方法 ...

  2. JavaScript---动态加载script和style样式

    一个网页里面的内容理解为一个XML或者说网页本身也就是一个XML文档,XML文档都有很特殊的象征:"标签"也叫"节点". 一个基本的网页格式 <!DOCT ...

  3. Dynamics 365触发Microsoft Flow自动生成PDF并作为附件送邮件

    我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...

  4. Ubantu 安装SSH

    1.检查是否安装SSH dpkg --get-selections | grep ssh 一般情况下Ubantu 默认集成 openssh-client,但要用sftp的话还需要安装openssh-s ...

  5. bay——RAC_ASM ORA-15001 diskgroup DATA does not exist or is not mounted.docx

    RAC ORA-15001: diskgroup "DATA" does not exist or is not mounted Oracle数据库识别不了存储Diskgroup ...

  6. 测试IP的一些网址

    http://httpbin.org/ip http://ip111.cn http://test.abuyun.com https://www.whatismybrowser.com

  7. 001 C/C++ 选择排序法

    简单选择排序: 选择排序法 是对 定位比较交换法(也就是冒泡排序法) 的一种改进. 选择排序的基本思想是:每一趟在n-i+1(i=1,2,…n-1)个记录中选取关键字最小的记录作为有序序列中第i个记录 ...

  8. JVM-4-堆内存划分

    什么是堆内存划分     Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,   一般分为新生代.老年代和永久代,这就是JVM的内存分代策略.(JDK 1.8之后将最初的永久代取消了,由元空间 ...

  9. 机器学习--支持向量机 (SVM)算法的原理及优缺点

    一.支持向量机 (SVM)算法的原理 支持向量机(Support Vector Machine,常简称为SVM)是一种监督式学习的方法,可广泛地应用于统计分类以及回归分析.它是将向量映射到一个更高维的 ...

  10. leetcode 5199. 交换字符串中的元素

    地址  https://leetcode-cn.com/contest/weekly-contest-155/problems/smallest-string-with-swaps/ 给你一个字符串  ...