在mapreduce中操作迭代器

时间:2010-08-14 03:44:42

标签: hadoop mapreduce parallel-processing

我试图使用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

4 个答案:

答案 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,了解为什么我们不能将其作为前向迭代器实现。)