• 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
     
  • 相关阅读:
    原生js中,call(),apply(),bind()三种方法的区别
    jQuery回溯!!!!!!!
    java中异常类与类别
    Java 多线程(并发)
    Java中反射与常用方法
    漫谈计算机构成
    java语言特性(相比C++)
    初级排序算法学习笔记
    java中参数传递
    关于类的知识
  • 原文地址:https://www.cnblogs.com/asker009/p/11439087.html
Copyright © 2020-2023  润新知