【问题标题】:Hadoop mapreduce 2 files filtering?Hadoop mapreduce 2 文件过滤?
【发布时间】:2021-04-20 16:13:14
【问题描述】:

我需要打印那些没有订单号的客户的“姓名”。我知道我必须使用映射器方法来实例化变量。我也必须使用 2 个映射器,因为有 2 个输入文件。在减少阶段,我必须过滤掉没有 order.no 的客户。但是,如何过滤掉那些没有订单号的客户呢?

文件1.txt

 Cust.No. Name

 1        Adam
 2        Abe
 3        Alex
 4        Jones

文件2.txt

    Order.Num.    Cust.No.     Price
    01            1            5422
    02            1            23
    03            2            1265
    04            3            127

我做了什么

最初在 reducer 方法中,我循环了键并检查它是否与现有键匹配:

if (!(Data[0].equals("key")))
    {
        System.out.println(Data[1]);
    }

但是,它会打印每一行。

【问题讨论】:

    标签: java hadoop mapreduce mapper


    【解决方案1】:

    看起来像是一个常规的 reduce side join,所以它可能是一个简单的用例,但是这些计算往往在工作负载方面变得非常残酷。这意味着我们必须想办法偷工减料,以确保应用程序能够很好地扩展以适应更大规模的输入。

    为应用程序的执行节省时间/空间的最常见方法是尝试设计潜在的多个 MR 作业,以便我们可以在保留所有功能的同时“削减”一个或多个作业,或者尝试最小化将在输入数据中实现的(自定义)映射器的数量。两者中的后者对于您试图实现的这种过滤非常常见,因为我们可以轻松地只使用一个 Map 函数,它的每个实例都会检查它当前正在读取的文件的名称以采取相应的行动。

    更具体地说,我们可以通过Map类的setup函数在mappers开始运行之前获取File1.txtFile2.txt文件名,并使用当前要读取的文件名来判断如何将文件中的数据切碎并存储到键值对中。对于您的问题,此 Map 函数将输出两种类型的键值对:

    • <customer_ID, customer_name>File1.txt 中的数据)

    • <customer_ID, order_ID>File2.txt 中的数据)

    然后,Reduce 函数的实例将为每个客户运行(因为客户 ID 和名称当然是唯一的)并访问分组值,这些值只不过是一些包含该客户名称的 Text 对象或订单 ID。我们只想输出记录中没有任何订单的客户,所以我们要做的就是检查这个值列表的长度是否为1(也就是这个客户除了那个和他的之外没有配对值)名称)。

    为了展示这一点,我将两个输入文件都放在 HDFS 中的目录 /input 中(我对 File1.txt 中的列使用了两个制表符分隔符,对 File2.txt 的列使用了三个制表符分隔符。在如果您的文件在列之间有不同的制表符或空格,您可以相应地更改它们):

    文件 1.txt

    Cust.No Name
    1       Adam
    2       Abe
    3       Alex
    4       Jones
    

    文件2.txt

    Order.Num.  Cust.No.    Price
    01          1           5422
    02          1           23
    03          2           1265
    04          3           127
    

    进行过滤的程序可能如下所示:

    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.fs.FileSystem;
    import org.apache.hadoop.fs.Path;
    import org.apache.hadoop.io.LongWritable;
    import org.apache.hadoop.io.Text;
    import org.apache.hadoop.mapreduce.InputSplit;
    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.input.FileSplit;
    import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    public class OrderListFilter
    {
        /* input:  <byte_offset, line_of_dataset>
         * output: <customer_ID, customer_name> OR <customer_ID, order_ID>
         */
        public static class Map extends Mapper<LongWritable, Text, Text, Text>
        {
            private String current_filename = "";
    
            protected void setup(Context context)
            {
                // get the name of the current to-be-read file
                InputSplit split = context.getInputSplit();
                Path path = ((FileSplit) split).getPath();
                current_filename = path.getName();
            }
    
            public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException
            {
                if(current_filename.equals("File1.txt"))    // if mapper is reading through the customer's file
                {
                    if(value.toString().contains("Cust.No"))    // remove header
                        return;
                    else
                    {
                        String[] columns = value.toString().split("\t\t");  // 2 tabs as delimiter
    
                        // write customer ID as key and name as value
                        context.write(new Text(columns[0]), new Text(columns[1]));
                    }
                }
                else if(current_filename.equals("File2.txt"))   // if mapper is reading through the order's file
                {
                    if(value.toString().contains("Cust.No"))    // remove header
                        return;
                    else
                    {
                        String[] columns = value.toString().split("\t\t\t"); // 3 tabs as delimiter
    
                        // write customer ID as key and order num as value
                        context.write(new Text(columns[1]), new Text(columns[0]));
                    }
                }
            }
        }
    
        /* input: <customer_ID, customer_name> OR <customer_ID, order_ID>
         * output: <customer_ID, customer_name>
         */
        public static class Reduce extends Reducer<Text, Text, Text, Text>
        {
            public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException
            {
                List<String> customer_records = new ArrayList<String>();
    
                // put all the values in a list to find the size of them
                for(Text value : values)
                    customer_records.add(value.toString());
    
                // if there's only one record, i.e. just the ID and the customer's name in they key-value pairs,
                // write their ID and name to output
                if(customer_records.size() == 1)
                    context.write(key, new Text(customer_records.get(0)));
            }
        }
    
    
        public static void main(String[] args) throws Exception
        {
            // set the paths of the input and output directories in the HDFS
            Path input_dir = new Path("input");
            Path output_dir = new Path("output");
    
            // in case the output directory already exists, delete it
            Configuration conf = new Configuration();
            FileSystem fs = FileSystem.get(conf);
            if(fs.exists(output_dir))
                fs.delete(output_dir, true);
    
            // configure the MapReduce job
            Job job = Job.getInstance(conf, "Order List Filter");
            job.setJarByClass(OrderListFilter.class);
            job.setMapperClass(Map.class);
            job.setReducerClass(Reduce.class);
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(Text.class);
            job.setOutputKeyClass(Text.class);
            job.setOutputValueClass(Text.class);
            FileInputFormat.addInputPath(job, input_dir);
            FileOutputFormat.setOutputPath(job, output_dir);
            job.waitForCompletion(true);
        }
    }
    
    

    它的输出似乎还可以(忽略我设置中的警告):

    【讨论】:

    • 在这种情况下我必须使用 2 个映射器吗?
    • 取决于“映射器”的含义。如果您谈论两个不同的地图功能,那么您可以看到没有必要。如果您谈论 Map 函数的两个实例,由于其每个文件的映射拆分机制,Hadoop 本身已经处理了这一点(这就是我们使用 FileInputFormat 类处理输入的原因)。
    • 另一种方法,我可以计算订单ID匹配的次数并显示匹配的次数为0吗?
    • 订单 ID 总是匹配的,因为它们在记录中总是有一个客户 ID,但是您试图找到不在任何订单中的客户,所以这种替代方法似乎没有完全正确。在 MapReduce 中,一般来说,我们将要输出的典型唯一数据作为键,所以我们在这里使用客户 ID,因为我们知道它每次都会明确地引用一个客户。
    • 如果我只需要名字而不需要 ID 怎么办?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多