问题:我们想要获取存储在文本文件中的平均工资。假设该文件包含firstname、lastname和salary。比方说,我们希望为美国所有规模的公司都这样做。新的一天会启动一个新文件,即4月29日输入的所有工资都在一个名为April29.txt的文件中,4月30日输入的工资都在名为Apry40.text的文件中等等。你可以想象,每天的行号都会不同。
目标:使用mapreduce计算每个文件的平均工资。
现在,无论我在哪里看,做平均的总体建议是:map一次读取一行并输出"key",value因为只有一个键-"键",所以所有输出都到一个reducer,在那里我们使用for循环来计算平均值。
这种方法很好,只是文件越大,计算时间就越差。有没有办法改善这种情况?我没有找到解决这种情况的例子,但如果你知道一些,请分享一个链接。提前谢谢。
这肯定可以更有效地完成。
现在,我们知道Mapper
有一个可以重写的map
方法。然而,它也有一个cleanup
。查看映射器的来源,您可以看到:
public void run(Context context) throws IOException, InterruptedException {
setup(context);
while (context.nextKeyValue()) {
map(context.getCurrentKey(), context.getCurrentValue(), context);
}
cleanup(context);
}
因此,我们可以使用这种清理方法来稍微优化我们的平均代码。
首先,您需要一个自定义的可写文件,它存储两个内容,count
和sum
。让我们称之为AverageWritable
。然后,我们将在映射器中执行类似的操作:
AverageWritable avg = new AverageWritable();
public void map(LongWritable key, Text value, Context ctx) {
long salary = [ ... code to get salary... ]
avg.addCount(1);
avg.addSum(salary);
}
public void cleanup(Context ctx) {
ctx.write(CONSTANT_KEY, avg);
}
reducer和combiner代码应该很容易从这里计算出来。
我很好奇我们能不能使用hadoop提供的计数器来完成这项工作。假设我们建造两个像这样的计数器
公共枚举计数计数器{柜台}
公共枚举SumCounters{柜台}
从我们的映射器的映射方法中,我们可以访问计数器并递增它
context.getCounter(CountCounters.Counter).increator(1);context.getCounter(SumCounters.Counter).increment();
最后我们将
job.getCounters().findCounter(CountCounters.Counter).getValue();job.getCounters().findCounter(SumCounters.Counter).getValue();
并找到平均