【问题标题】:Hadoop map-reducer is not writing any outputHadoop map-reducer 没有写入任何输出
【发布时间】:2016-12-07 04:26:04
【问题描述】:

我正在研究一个三节点 Hadoop mapreduce 问题,该问题旨在采用 200,000 行 input.csv 文件,其中日期和点值作为标题(25 行示例数据的要点:https://gist.githubusercontent.com/PatMulvihill/63effd90411efe858330b54a4111fadb/raw/4033695ba5ca2f439cfd1512358425643807d83b/input.csv)。程序应该找到任何不属于以下值的点值:200, 400, 600, 800, 1000, 1200, 1600, or 2000。该点值应该是值。键应该是该点值之前的值中从日期算起的年份。例如,如果我们有数据 2000-05-25,400 2001-10-12, 650 2001-04-09, 700 ,那么应该发送到reducer 的键值对是<2001, 650><2001, 700>。然后,reducer 应该取每个给定年份中所有值的平均值,并将这些键值对写入我指定的 hdfs /out 路径。该程序编译得很好,但实际上从未将任何内容写入输出。我想知道为什么以及我能做些什么来解决它。 完整代码如下:

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
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;

public class JeopardyMR {

public static class SplitterMapper extends Mapper <Object, Text, Text, IntWritable> {

    public void map (Object key, Text value, Context context) throws IOException, InterruptedException {
        // Convert the CSVString (of type Text) to a string
        String CSVString = value.toString();
        // Split the string at each comma, creating an ArrayList with the different attributes in each index.
        // Sometimes the questions will be split into multiple elements because they contain commas, but for the
        // way we will be parsing the CSV's, it doesn't matter.
        List<String> items = Arrays.asList(CSVString.split("\\s*,\\s*"));
        // Loop through all the elements in the CSV
        // Start i at 3 to ensure that you do not parse a point value that has a year absent from the data set.
        // We can end the loop at items.size() w/o truncating the last 3 items because if we have a point value, we know
        // that the corresponding year is in the items before it, not after it.
        // We will miss 1 or 2 data points because of this, but it shouldn't matter too much because of the magnitude of our data set
        // and the fact that a value has a low probability of actually being a daily double wager.
        for (int i = 3; i < items.size(); i++) {
            // We want a String version of the item that is being evaluated so that we can see if it matches the regex
            String item = items.get(i);
            if (item.matches("^\\d{4}\\-(0?[1-9]|1[012])\\-(0?[1-9]|[12][0-9]|3[01])$")) {
                // Make sure that we don't get an out of bounds error when trying to access the next item
                if (i + 1 >= items.size()) {
                    break;
                } else {
                    // the wagerStr should always be the item after a valid air date
                    String wagerStr = items.get(i + 1);
                    int wager = Integer.parseInt(wagerStr);
                    // if a wager isn't the following values, assume that is a daily double wager
                    if (wager != 200 && wager != 400 && wager != 600 && wager != 800 && wager != 1000 && wager != 1200 && wager != 1600 && wager != 2000) {
                        // if we know that a point value of a question is in fact a daily double wager, find the year that the daily double happened
                        // the year will always be the first 4 digits of a valid date formatted YYYY-MM-DD
                        char[] airDateChars = item.toCharArray();
                        String year = "" + airDateChars[0] + airDateChars[1] + airDateChars[2] + airDateChars[3];

                        // output the follow key-value pair: <year, wager>
                        context.write(new Text(year), new IntWritable(wager));
                    }
                }

            }
        }
    }
}

public static class IntSumReducer extends Reducer <Text, IntWritable, Text, IntWritable> {

    private IntWritable result = new IntWritable();
    public void reduce (Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum = 0, count = 0;
        for (IntWritable val : values) {
            sum += val.get();
            count++;
        }
        int avg = sum / count;
        result.set(avg);
        context.write(key, result);
    }
}

public static void main (String[] args) throws Exception {
    Configuration conf = new Configuration();
    Job job = Job.getInstance(conf, "jeopardy daily double wagers by year");
    job.setJarByClass(JeopardyMR.class);
    job.setMapperClass(SplitterMapper.class);
    job.setCombinerClass(IntSumReducer.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    FileInputFormat.addInputPath(job, new Path(args[0]));
    FileOutputFormat.setOutputPath(job, new Path(args[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}

编译成功的终端输出可以在这里找到:https://gist.github.com/PatMulvihill/40b3207fe8af8de0b91afde61305b187 我对 Hadoop map-reduce 非常陌生,我可能犯了一个非常愚蠢的错误。我将此代码基于此处找到的代码:https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html 如果我遗漏任何有用的信息,请告诉我。任何帮助,将不胜感激!谢谢。

【问题讨论】:

  • 尝试简化示例(不要做任何过滤)以检查数据是否会被写入。
  • 你可以检查一些东西来看看问题出在哪里:是你什么都没读吗?检查地图输入记录计数器。如果为零,请检查输入路径。是不是您没有从映射器中写任何东西(可能是)?检查地图输出记录计数器。尝试在 if 中打印一些检查模式匹配的内容并查看它是否已打印
  • @vefthym 我检查了地图输入记录计数器,它等于输入记录的数量,这是可以预期的。我通过格式化输入文件并将其重新添加到 hdfs 来确认输入路径是正确的。我认为我的问题是我没有从映射器到减速器写任何东西。映射输出记录计数器为 0,写入的字节数也是如此。这是我的程序编译成功时的终端消息输出:gist.github.com/PatMulvihill/40b3207fe8af8de0b91afde61305b187

标签: java csv hadoop mapreduce hadoop2


【解决方案1】:

我检查并认为 items.size() 是两个。如您所知,map 的输入是文件行,map 任务为每一行执行 map 函数。一旦每行用分号分割,项目的大小变为 2,当项目大小大于 3 时执行下一个。 您可以检查映射输出写入字节以查看是否有任何数据写入。 编辑 : 用这个替换地图代码:

public void map (Object key, Text value, Context context) throws IOException, InterruptedException {
        String CSVString = value.toString();
        String[] yearsValue =  CSVString.split("\\s*,\\s*");
        if(yearsValue.length == 2){
            int wager = Integer.parseInt(yearsValue[1]);
            if (wager != 200 && wager != 400 && wager != 600 && wager != 800 && wager != 1000 && wager != 1200 && wager != 1600 && wager != 2000) {
                char[] airDateChars = yearsValue[0].toCharArray();
                String year = "" + airDateChars[0] + airDateChars[1] + airDateChars[2] + airDateChars[3];
                context.write(new Text(year), new IntWritable(wager));

            }
        }else{
            System.out.println(CSVString);
        }
}

【讨论】:

  • 谢谢@vahid。我收回了我的“不回答”标志并投了反对票。但是,您的答案仍然是错误的,因为在这种情况下使用组合器不仅是正确的,而且是一个非常好的做法。请考虑删除这部分答案。
  • @vahid 知道为什么items.size() 只会是两个吗?我的理解是,当我将values 参数转换为字符串时,我将获取所有值以创建一个包含已提供给该特定节点的所有输入的巨型字符串。我是不是误会了什么?另外,你如何确定items.size() 是二?我的打印结果不适用于 hadoop 应用程序,并且它们最终不会出现在应有的日志文件中。
  • @Pat Mulvihill 我们有两个地图概念:1- 地图任务 2- 地图功能。 Mapreduce 基于配置分区输入数据并将其提供给映射任务,然后将任务分区输入数据库映射到新行并将每一行提供给映射功能。你写的是地图功能而不是地图任务。
【解决方案2】:

我实际上通过将我的.csv 文件转换为.txt 文件来解决这个问题。这不是问题的真正解决方案,但它是我的代码工作的原因,现在我可以继续理解为什么它是一个问题。另外,这可能会在将来对某人有所帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-25
    • 1970-01-01
    • 2018-02-10
    • 1970-01-01
    • 1970-01-01
    • 2014-03-19
    • 1970-01-01
    相关资源
    最近更新 更多