我真的陷入困境,我必须强制执行 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
函数,该函数以特定项开头。此外,我必须强制在map
和reduce
之间进行排序,以反向词典顺序对键进行排序。因此,单个减速器将为项目 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的Comparator
和Partitioner
。
更新------------------------------------------- -------------------------------------------------- ---------------------------
Asya Kamsky已经向我指出,finalize
每个键仅运行一次,因此它解决了分区问题,当一个reducer必须看到特定键的每个值时。
排序仍然是一个问题。对于大型数据集,在finalize
中实现我自己的排序意味着执行时间方面存在巨大瓶颈,而我没有在map
和reduce
之间使用自然排序机制。密钥的数据类型为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
内进行排序?
答案 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 ] }