hadoop mapreduce求解有序TopN
利用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的更多相关文章
- hadoop mapreduce求解有序TopN(高效模式)
1.在map阶段对数据先求解改分片的topN,到reduce阶段再合并求解一次,求解过程利用TreeMap的排序特性,不用自己写算法. 2.样板数据,类似如下 1 13682846555 192.16 ...
- Hadoop Mapreduce分区、分组、二次排序过程详解[转]
原文地址:Hadoop Mapreduce分区.分组.二次排序过程详解[转]作者: 徐海蛟 教学用途 1.MapReduce中数据流动 (1)最简单的过程: map - reduce (2) ...
- 从分治算法到 Hadoop MapReduce
从分治算法说起 要说 Hadoop MapReduce 就不得不说分治算法,而分治算法其实说白了,就是四个字 分而治之 .其实就是将一个复杂的问题分解成多组相同或类似的子问题,对这些子问题再分,然后再 ...
- 三种方法实现Hadoop(MapReduce)全局排序(1)
我们可能会有些需求要求MapReduce的输出全局有序,这里说的有序是指Key全局有序.但是我们知道,MapReduce默认只是保证同一个分区内的Key是有序的,但是不保证全局有序.基于此,本文提供三 ...
- 分别使用Hadoop和Spark实现TopN(1)——唯一键
0.简介 TopN算法是一个经典的算法,由于每个map都只是实现了本地的TopN算法,而假设map有M个,在归约的阶段只有M x N个,这个结果是可以接受的并不会造成性能瓶颈. 这个TopN算法在ma ...
- Hadoop MapReduce 一文详解MapReduce及工作机制
@ 目录 前言-MR概述 1.Hadoop MapReduce设计思想及优缺点 设计思想 优点: 缺点: 2. Hadoop MapReduce核心思想 3.MapReduce工作机制 剖析MapRe ...
- hadoop MapReduce运营商案例关于用户基站停留数据统计
注 如果需要文件和代码的话可评论区留言邮箱,我给你发源代码 本文来自博客园,作者:Arway,转载请注明原文链接:https://www.cnblogs.com/cenjw/p/hadoop-mapR ...
- Hadoop - MapReduce 过程
Hadoop - MapReduce 一.MapReduce设计理念 map--->映射 reduce--->归纳 mapreduce必须构建在hdfs之上的一种大数据离线计算框架 在线: ...
- Hadoop MapReduce执行过程详解(带hadoop例子)
https://my.oschina.net/itblog/blog/275294 摘要: 本文通过一个例子,详细介绍Hadoop 的 MapReduce过程. 分析MapReduce执行过程 Map ...
随机推荐
- 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 ...
- Oracle merge into的优势
简介 Oracle merge into命令,顾名思义就是“有则更新,无则插入”,这个也是merge into 命令的核心思想,在实际开发过程中,我们会经常遇到这种通过两表互相关联匹配更新其中一个表的 ...
- 设计院老师良心汇总:值得牢记的15个CAD基础技巧,能帮大忙
哈喽!你们的CAD魔鬼(老师)来喽! 良心CAD技巧汇总,设计院师傅精心汇总,值得你牢记的15个CAD基础技巧,满满的都是干货,日常最常见的问题以及解决方法这里都汇总给你,给你高效的绘图体验,关键时刻 ...
- cookie --中间件
Cookie简介 cookie是服务器存储在用户计算机中的变量,可以让我们用同一个浏览器访问同一个域名的时共享数据. HTTP是一种无状态协议,简单来说,当你从一个页面,然后跳转到同站点的另一个页面时 ...
- 【ASP.NET Core】AddMvc和AddMvcCore的区别
AddMvcCore() method only adds the core MVC services. AddMvc() method adds all the required MVC servi ...
- Dojo.declare使用方法详解
ArcGIS API for JavaScript是基于dojo开发的一套API,在实际生产中,我们需要再根据自己的需求实现自定义的功能,最后抽象成接口给前端调用. 我们使用dojo的declare来 ...
- MySQL 优化 (二)
参数优化 Max_connections (1)简介 Mysql的最大连接数,如果服务器的并发请求量比较大,可以调高这个值,如果连接数越来越多,mysql会为每个连接提供单独的缓冲区,就会开销的越多的 ...
- PyCharm批量修改变量名
方法和 PyCharm重命名文件时更改引用的地方相同
- 游戏AI之A*寻路算法(3)
前言:寻路是游戏比较重要的一个组成部分.因为不仅AI还有很多地方(例如RTS游戏里操控人物点到地图某个点,然后人物自动寻路走过去)都需要用到自动寻路的功能. 本文将介绍一个经常被使用且效率理想的寻路方 ...
- 10. java 匿名对象说明
一.匿名对象 public class Demo{ public static void main(String[] args){ Person one = new Person(); one.nam ...