MongoDB的mapReduce:分区键到单个reducer并影响键排序

时间:2013-10-14 12:30:09

标签: mongodb mapreduce

我真的陷入困境,我必须强制执行 mapReduce 框架,才能对特定密钥使用一个reducer。另外,我想影响框架如何对键进行排序。我将在一个例子中介绍这个问题:

我想以下列形式发出键值对:

  

< b x b > :< d1>
     < b x > :< d2>
     < b > :< d3>
     < b a x > :< d2,d3>
     图1

密钥是一个序列 - 正如您所见 - 每个都以项目 b 开头,这将是一种数据类型string。如字母 d 和数字所示,值为ObjectID s。我从map函数中发出了其他键值对,它们的键中包含不同的项,例如 a x

  

< a b x > :< d1>
     < a x > :< d3>
     < x a a > :< d3>
     图2

我需要强制框架为每个键值对调用一个reduce函数,该函数以特定项开头。此外,我必须强制在mapreduce之间进行排序,以反向词典顺序对键进行排序。因此,单个减速器将为项目 b

接收以下键值对
  

< b x b > :< d1>
     < b x > :< d2>
     < b a x > :< d2,d3>
     < b > :< d3>
     图3

我尝试了什么:

我尝试以下列形式发出键值对:

  

b :< (d1:< b x b >)>
      b :< (d2:< b x >)>
      b :< (d3:< b >)>
      b :< (d2:< b a x >),(d3:< b a x >)>
     图4

这样一个减速器就会收到 b 项的值,但是你没有看到反向词典顺序,最糟糕的是,不能保证单个减速器会得到特定键的所有值(as MongoDB's MapReduce documentation states)。

基本上:我必须以反向字典顺序处理以特定项目开头的序列。

我没有想法会让我进一步解决问题。如何为密钥强制执行单个reducer并影响排序? 我应该如何设计传递(发布)的数据结构以满足我的需求?

这些功能类似于Hadoop的ComparatorPartitioner

更新------------------------------------------- -------------------------------------------------- ---------------------------

Asya Kamsky已经向我指出,finalize每个键仅运行一次,因此它解决了分区问题,当一个reducer必须看到特定键的每个值时。

排序仍然是一个问题。对于大型数据集,在finalize中实现我自己的排序意味着执行时间方面存在巨大瓶颈,而我没有在mapreduce之间使用自然排序机制。密钥的数据类型为string,但很容易将它们编码为负integers以强制进行反向排序。

让我们再次检查图3

  

< b x b > :< d1>
     < b x > :< d2>
     < b a x > :< d2,d3>
     < b > :< d3>
     图3

这是关键 b 必须获得的finalize。密钥,例如< b x b >在这里是复合的。 Finalize需要接收以 b 开头的密钥,但是对于密钥的其他部分,则采用反向字典顺序。

有没有办法实现这一点并避免在finalize内进行排序?

1 个答案:

答案 0 :(得分:3)

您可以做的是“正常”发出文档,并使用reduce将所有发出的值组合成一个已排序的数组。然后使用finalize方法在单个reducer中执行您要执行的任何处理。

MongoDB reduce函数可以多次调用,但也可以永远调用(如果只为特定键发出一个值)。使用finalize可以解决这两个问题,因为每个键只调用一次。

示例数据:

> db.sorts.find()
{ "_id" : 1, "b" : 1, "a" : 20 }
{ "_id" : 2, "b" : 1, "a" : 2 }
{ "_id" : 3, "b" : 2, "a" : 12 }
{ "_id" : 4, "b" : 3, "a" : 1 }
{ "_id" : 5, "b" : 2, "a" : 1 }
{ "_id" : 6, "b" : 3, "a" : 11 }
{ "_id" : 7, "b" : 3, "a" : 5 }
{ "_id" : 8, "b" : 2, "a" : 1 }
{ "_id" : 9, "b" : 1, "a" : 15 }

地图功能:

map = function() {
   emit( this.b, { val: [ this.a ] } );
}

通过遍历数组减少将新传入的val添加到已排序数组中的函数:

reduce = function( key, values) {
   var result = { val: [ ] };
   values.forEach(function(v) {
      var newval = v.val[0];
      var added = false;
      for (var i=0; i < result.val.length; i++) {
           if (newval < result.val[i]) {
                 result.val.splice(i, 0, newval);
                 added=true;
                 break;
           }
      }
      if ( !added ) {
         result.val.splice(result.val.length, 0, newval);
      }
   });
   return result;
}

Finalize只返回一个简单的数组:

finalize = function( key, values ) {
   // values is document with a sorted array
   // do your "single reduce" functionality here
   return values.val;
}

运行MapReduce:

> db.sorts.mapReduce(map, reduce, {out:"outs", finalize:finalize})
{
    "result" : "outs",
    "timeMillis" : 10,
    "counts" : {
        "input" : 9,
        "emit" : 9,
        "reduce" : 3,
        "output" : 3
    },
    "ok" : 1,
}

结果是:

> db.outs.find()
{ "_id" : 1, "value" : [  2,  15,  20 ] }
{ "_id" : 2, "value" : [  1,  1,  12 ] }
{ "_id" : 3, "value" : [  1,  5,  11 ] }