我试图使用hadoop找到任何给定点的总和,我遇到的问题是从单个reducer中获取给定键的所有值。它看起来像这样。
减速机:
public static class Reduce extends MapReduceBase implements
Reducer<Text, IntWritable, Text, DoubleWritable> {
public void reduce(Text key, Iterator<IntWritable> values,
OutputCollector<Text, DoubleWritable> output, Reporter reporter)
throws IOException {
Text word = new Text();
Iterator<IntWritable> tr = values;
IntWritable v;
while (tr.hasNext()) {
v = tr.next();
Iterator<IntWritable> td = values;
while (td.hasNext()) {
IntWritable u = td.next();
double sum = u+v;
word.set( u + " + " + v);
output.collect(word, new DoubleWritable(sum));
}
}
}
}
我正在尝试创建Iterator变量的两个副本,以便我可以遍历第二个迭代器的所有值,同时从前一个Iterator获取单个值(两个while循环)但是两个迭代器保持同等价值。
我不确定这是否是正确的方法,任何帮助都非常感谢。
谢谢,
Tsegay
答案 0 :(得分:29)
reducer中的迭代器并不像你想象的那么简单。
问题是您迭代的项目总数可能不适合内存。这意味着迭代器可能正在从磁盘读取。如果你有两个独立的迭代器副本,那么你可以让它们中的一个远远超过另一个,这意味着两个迭代器之间的数据不能被删除。
为了简化实现,Hadoop不支持为reduce值使用多个迭代器。
这样做的实际影响是你不能两次通过相同的迭代器。这不好,但情况确实如此。如果您完全知道项目的数量将适合内存,那么您可以按照MrGomez的建议将所有项目复制到列表中。如果您不知道,可能必须使用二级存储。
更好的方法是重新设计程序,以便在reducer中不需要无限存储。这可能会有点棘手,但有一些标准方法可以解决这个问题。
对于您的特定问题,相对于最大减少输入集,输出大小会有二次增长。这通常是一个非常糟糕的主意。在大多数情况下,您不需要所有对,只需要最重要的对。如果您可以以某种方式修剪对的集合,那么您已经设置好了,并且您可以删除所有对约束。
例如,如果您尝试查找每个reduce集的最大总和的100对,则可以保留一个优先级队列,其中包含到目前为止看到的100个最大输入,以及到目前为止看到的100个最大总和的优先级队列。对于每个新输入,您可以形成目前为止看到的最大100个数字的总和,并尝试将这些总和粘贴到第二个队列中。最后,您应该将新输入粘贴到第一个队列中,并通过删除最小值(如果需要)将两个队列修剪为100个元素。在reduce的close方法中,您应该转储优先级队列。这种方法保证您只需要min(n ^ 2,200)个存储元素,避免了n ^ 2问题,并通过保持看到的100个最大项目而不是所有项目来避免双重传递。
答案 1 :(得分:12)
我不确定你要完成的是什么,但我知道的很多:Hadoop迭代器的行为有点奇怪。调用Iterator.next()将始终返回IntWritable的SAME EXACT实例,该实例的内容将替换为下一个值。因此,在对Iterator.next()的调用中保持对IntWritable的引用几乎总是一个错误。我相信这种行为是设计用于减少对象创建量和GC开销。
解决此问题的一种方法是使用WritableUtils.clone()来克隆您在Iterator.next()调用时尝试保留的实例。
答案 2 :(得分:2)
要复制Iterator,您不能将迭代器分配给新变量。您应该将迭代器“克隆”到迭代器类的新变量。 当迭代器A分配另一个迭代器变量B时,迭代器的两个变量指向相同的数据。
答案 3 :(得分:1)
经过your previous question,你似乎被卡在the iterator problem piccolbo described.你的减速器的表述也表明你已经放弃了他提出的天真方法的算法......虽然不是最理想的,但它会起作用。 / p>
请允许我用我的回答清理你的代码:
// Making use of Hadoop's Iterable reduce, assuming it's available to you
//
// The method signature is:
//
// protected void reduce(KEYIN key, java.lang.Iterable<VALUEIN> values,
// org.apache.hadoop.mapreduce.Reducer<KEYIN,VALUEIN,KEYOUT,VALUEOUT>.Context
// context) throws java.io.IOException, java.lang.InterruptedException
//
public void reduce(Text key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
// I assume you declare this here to save on GC
Text outKey = new Text();
IntWritable outVal = new IntWritable();
// Since you've forgone piccolbo's approach, you'll need to maintain the
// data structure yourself. Since we always walk the list forward and
// wish to optimize the insertion speed, we use LinkedList. Calls to
// IntWritable.get() will give us an int, which we then copy into our list.
LinkedList<Integer> valueList = new LinkedList<Integer>();
// Here's why we changed the method signature: use of Java's for-each
for (IntWritable iw: values) {
valueList.add(iw.get());
}
// And from here, we construct each value pair as an O(n^2) operation
for (Integer i: valueList) {
for (Integer j: valueList) {
outKey.set(i + " + " + j);
outVal.set(i + j);
context.write(outKey, outVal);
}
}
// Do note: I've also changed your return value from DoubleWritable to
// IntWritable, since you should always be performing integer operations
// as defined. If your points are Double, supply DoubleWritable instead.
}
这有效,但它在构造距离矩阵时会做出一些限制性能的假设,包括要求在单个reduce操作中执行组合。
如果您事先知道输入数据集的大小和维度,请考虑piccolbo's approach。在最坏的情况下,通过在线性时间内走输入线,这应该是可用的。
(参见this thread,了解为什么我们不能将其作为前向迭代器实现。)