基于MapReduce的TOP-K查询问题的实现

TreeMap存储前K个数据

如何存储前K个数据是TopK问题的一大核心问题,这里采用Java中TreeMap来进行存储。TreeMap的实现是红黑树算法的实现,红黑树又称红-黑二叉树,它首先是一棵二叉树,它具体二叉树所有的特性,同时红黑树更是一棵自平衡的排序二叉树。平衡二叉树必须具备如下特性:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。也就是说该二叉树的任何一个等等子节点,其左右子树的高度都相近。平衡二叉树图如下图1所示。红黑树顾名思义就是:节点是红色或者黑色的平衡二叉树,它通过颜色的约束来维持着二叉树的平衡。具有下列特性:1.节点是红色或黑色。2.根节点是黑色。3.每个叶子节点都是黑色的空节点(NIL节点)。4.每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)。5.从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。如下图2所示。

平衡二叉树

红黑树

在TreeMap的put()的实现方法中主要分为两个步骤,第一:构建排序二叉树,第二:平衡二叉树。为了平衡二叉树,往往需要进行左旋和右旋以及着色操作。这些操作的目的都是为了维持平衡,保证二叉树是有序的,可以帮助我们实现有序的效果,即数据的存储是有序的。

实验环境

搭建实验环境所需操作系统为Ubuntu15,所用软件包括jdk1.7.0,Hadoop-2.9.0。相关的实验环境配置等问题是按照实验课进行配置的,此处不再赘述。Hadoop的启动,进入/usr/local/Hadoop/sbin目录下,通过./start-all.sh命令启动Hadoop。输入jps查看启动是否成功。如下图所示为启动Hadoop截图。
这里写图片描述
实验数据为存有一百万海量数据的squence.txt文档。通过TOP-K找出其中的前K大或者前K小的数。文档内容如图所示。具体附于实验代码的data文件夹中。数据来源:http://pan.baidu.com/s/1qWt4WaS
这里写图片描述

实验过程

实验以求100万海量数据的前K个最大的数据为例。进行map函数和reduce函数的编写。
(1)map阶段
通过map方法将数据构造成大小小于K的红黑树,在每次map后判断树的大小和K的大小,当红黑树的数据量大于K时,取出最大的数。在map方法结束后会执行cleanup方法,该方法将map任务中的前K个数据传入reduce任务中.
(2)reduce阶段
在reduce方法中,依次将map方法中传入的K个数据放入TreeMap中,并依靠红黑色的平衡特性来维持数据的有序性。从而将K个数据利用红黑树的firstKey方法按从大到小或者利用红黑树的lastKey方法按从小到大的顺序排列。从而求出前K个数。在run方法中,判断输出路径是否已存在,如果已存在,则删除。此外还设置自定义Mapper和Reducer以及输出目录等。
代码部分:

import java.net.URI;
import java.util.TreeMap;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.compress.CompressionCodec;
import org.apache.hadoop.io.compress.GzipCodec;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

public class MyTopKNumJob extends Configured implements Tool {

    /**
     * @author SCX
     */
    public static class MyMapper extends
            Mapper<LongWritable, Text, NullWritable, LongWritable> {
        public static final int K =8 ;
        private TreeMap<Long, Long> tm = new TreeMap<Long, Long>();

        protected void map(
                LongWritable key,
                Text value,
                Mapper<LongWritable, Text, NullWritable, LongWritable>.Context context)
                throws java.io.IOException, InterruptedException {
            try {
                long temp = Long.parseLong(value.toString().trim());
                tm.put(temp, temp);
                if (tm.size() > K) {

                    // 如果是求topk个最大的那么使用下面的语句
                    tm.remove(tm.firstKey());
                    //如果是求topk个最小的那么使用下面的语句
                  //  tm.remove(tm.lastKey());
                }
            } catch (Exception e) {
                context.getCounter("TopK", "errorLog").increment(1L);
            }
        };

        protected void cleanup(
                org.apache.hadoop.mapreduce.Mapper<LongWritable, Text, NullWritable, LongWritable>.Context context)
                throws java.io.IOException, InterruptedException {
            for (Long num : tm.values()) {
                context.write(NullWritable.get(), new LongWritable(num));
            }
        };
    }

    public static class MyReducer extends
            Reducer<NullWritable, LongWritable, NullWritable, LongWritable> {
        public static final int K = 8;
        private TreeMap<Long, Long> tm = new TreeMap<Long, Long>();

        protected void reduce(
                NullWritable key,
                java.lang.Iterable<LongWritable> values,
                Reducer<NullWritable, LongWritable, NullWritable, LongWritable>.Context context)
                throws java.io.IOException, InterruptedException {
            for (LongWritable num : values) {
                tm.put(num.get(), num.get());
                if (tm.size() > K) {
                    //tm.remove(tm.firstKey());
                    // 如果是求topk个最小的那么使用下面的语句
                    tm.remove(tm.lastKey());
                }
            }
            // 按降序即从大到小排列Key集合
            for (Long value : tm.descendingKeySet()) {
                context.write(NullWritable.get(), new LongWritable(value));
            }
        };
    }

    // 输入文件路径
    public static String INPUT_PATH = "hdfs://localhost:9000/user/hadoop/sequence.txt";
    // 输出文件路径s
    public static String OUTPUT_PATH = "hdfs://localhost:9000/user/hadoop/topk8";

    @Override
    public int run(String[] args) throws Exception {
        // 首先删除输出路径的已有生成文件
        FileSystem fs = FileSystem.get(new URI(INPUT_PATH), getConf());
        Path outPath = new Path(OUTPUT_PATH);
        if (fs.exists(outPath)) {
            fs.delete(outPath, true);

        }

        Job job = new Job(getConf(), "TopKNumberJob");
        // 设置输入目录
        FileInputFormat.setInputPaths(job, new Path(INPUT_PATH));
        // 设置自定义Mapper
        job.setMapperClass(MyMapper.class);
        job.setMapOutputKeyClass(NullWritable.class);
        job.setMapOutputValueClass(LongWritable.class);
        // 设置自定义Reducer
        job.setReducerClass(MyReducer.class);
        job.setOutputKeyClass(NullWritable.class);
        job.setOutputValueClass(LongWritable.class);
        // 设置输出目录
        FileOutputFormat.setOutputPath(job, new Path(OUTPUT_PATH));

        return job.waitForCompletion(true) ? 0 : 1;
    }

    public static void main(String[] args) {
        Configuration conf = new Configuration();
        // map端输出启用压缩
        conf.setBoolean("mapred.map.output", true);
        // reduce端输出启用压缩
       conf.setBoolean("mapred.output", true);
        // reduce端输出压缩使用的类
        conf.setClass("mapred.output.codec", GzipCodec.class,CompressionCodec.class);

        try {
            int res = ToolRunner.run(conf, new MyTopKNumJob(), args);
            System.exit(res);
            } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

实验结果

(1)令K=10,找出一百万海量数据的前10个小数据。实验结果如图所示。
这里写图片描述
(2)令K=100,找出一百万海量数据的前100个小数据。实验结果如图所示。
这里写图片描述
(3)令K=8,找出一百万海量数据的前8个大数据。实验结果如图所示。
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值