我有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)));
});
}
}
答案 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任务处理。