结合hadoop map-reduce的结果

时间:2015-07-29 13:48:21

标签: java hadoop mapreduce

我有Mapper<AvroKey<Email>, NullWritable, Text, Text>有效接收电子邮件,多次吐出电子邮件地址的密钥和找到的字段的值(从,到,抄送等)。

然后我有一个Reducer<Text, Text, NullWritable, Text>,它接收电子邮件地址和字段名称。它会吐出一个NullWritable键,并计算给定字段中地址的存在次数。 e.g ...

{
  "address": "joe.bloggs@gmail.com",
  "toCount": 12,
  "fromCount": 4
}

我正在使用FileUtil.copyMerge来混淆作业的输出,但(很明显)不同的缩减器的结果没有合并,所以在实践中我看到:

{
  "address": "joe.bloggs@gmail.com",
  "toCount": 12,
  "fromCount": 0
}, {
  "address": "joe.bloggs@gmail.com",
  "toCount": 0,
  "fromCount": 4
}

是否有更合理的方法来解决此问题,以便每个电子邮件地址可以获得一个结果? (我收集一个运行pre-reduce阶段的组合器只运行在一个数据的子集上,并不能保证给出我想要的结果)?

编辑:

Reducer代码类似于:

public class EmailReducer extends Reducer<Text, Text, NullWritable, Text> {

    private static final ObjectMapper mapper = new ObjectMapper();

    public void reduce(Text key, Iterable<Text> values, Context context)
            throws IOException, InterruptedException {
        Map<String, Map<String, Object>> results = new HashMap<>();

        for (Text value : values) {
            if (!results.containsKey(value.toString())) {
                Map<String, Object> result = new HashMap<>();
                result.put("address", key.toString());
                result.put("to", 0);
                result.put("from", 0);

                results.put(value.toString(), result);
            }

            Map<String, Object> result = results.get(value.toString());

            switch (value.toString()) {
            case "TO":
                result.put("to", ((int) result.get("to")) + 1);
                break;
            case "FROM":
                result.put("from", ((int) result.get("from")) + 1);
                break;
        }

        results.values().forEach(result -> {
            context.write(NullWritable.get(),  new Text(mapper.writeValueAsString(result)));
        });
    }
}

1 个答案:

答案 0 :(得分:1)

reducer的每个输入键对应一个唯一的电子邮件地址,因此您不需要results集合。每次调用reduce方法时,都是针对不同的电子邮件地址,所以我的建议是:

public class EmailReducer extends Reducer<Text, Text, NullWritable, Text> {

  private static final ObjectMapper mapper = new ObjectMapper();

  public void reduce(Text key, Iterable<Text> values, Context context)
        throws IOException, InterruptedException {

    Map<String, Object> result = new HashMap<>(); 
    result.put("address", key.toString());
    result.put("to", 0);
    result.put("from", 0);

    for (Text value : values) {
        switch (value.toString()) {
        case "TO":
            result.put("to", ((int) result.get("to")) + 1);
            break;
        case "FROM":
            result.put("from", ((int) result.get("from")) + 1);
            break;
    }

    context.write(NullWritable.get(),  new Text(mapper.writeValueAsString(result)));

  }
}

我不确定ObjectMapper类是做什么的,但我想你需要它来格式化输出。否则,我将打印输入密钥作为输出密钥(即电子邮件地址)和每个电子邮件地址的“从”和“到”字段的两个连接计数。

如果您的输入是数据集合(即,不是流,或类似的smth),那么您应该只获取每个电子邮件地址一次。如果您的输入是在流中给出的,并且您需要逐步构建最终输出,那么一个作业的输出可以是另一个作业的输入。如果是这种情况,我建议使用MultipleInputs,其中一个Mapper是您之前描述的Mapper和另一个IdentityMapper,将前一个作业的输出转发给Reducer。这样,同一个电子邮件地址也会由同一个reduce任务处理。