Mongodb Mapreduce讨论及分享(平均数和唯一数统计误区)

FLZYUP 发布于 2012/04/19 12:15
阅读 2K+
收藏 6

最近在做mongodb的mapreduce的统计,发现mapreduce里面存在这很多容易陷入的陷阱

特别是在统计平均数和唯一数的时候如果在reduce里面写了统计平均数和唯一数的计算,很有可能计算出来的结果是错误的。

mongodb官方文档里面有一句“In fact, the reduce function may have to run more than once”,就是说在执行map reduce运算的时候reduce方法也许会执行多次。

误区:在reduce里面统计平均数和唯一数

例如:需要统计progress的平均数和ip的唯一数

map里面:

emit(key, {count:1, progress: this.progress, ip:this.ip, ipCount: this.ip ? 1 : 0}) 

reduce里面:

function Reduce(key, values) {
    var r = {count:0, progress:0};
    var progresTotal = 0;
    var ips = {};
    var ipCount = 0;
    values.forEach(function(val) {
        r.count += val.count;
        progressTotal += val.progress;
        if (!ips[val.ip]) {
            ips[val.ip] = 0;
            ipCount++;
        }
    });
    r.progress = progresTotal/r.count;
    r.ipCount = ipCount;
}

你会发现这样计算出来的progress的值 有些时候是对的 有些时候是错的,另外ip唯一数统计出来也有可能是部分正确部分错误,这是因为reduce被执行了多次的时候上面的算法明显错误了。

正确的方法应该是在Finalize的时候执行平均数的统计:

function Map() {
   emit(key, {count:1, progress: this.progress, ip:this.ip, ipCount: {}}) 
}
function Reduce(key, values) {
    var r = {count:0, progress:0};
    values.forEach(function(val) {
        r.count += val.count;
        r.progress += val.progress;
        if (val.ip && val.ip != 0 && !r.ipCount[val.ip]) {
            r.ipCount[val.ip] = 0;
        }
    });
}

function Finalize(key, reduced) {
    reduced.progress = reduced.progress/reduced.count;
    var count = 0;
    if (var ip in reduced.ipCount) {
        count++;
    }
    //这个if需要注意
    if (count == 0 && reduced.count == 1 &&  reducedIp && reduced.Ip != 0) {
        count++;
    }     
    reduced.ipCount = count;
    return reduced;
}

注意上面注释“ 这个if需要注意”部分,在我写mapreduce的调试中, 当某个key的document只有一个的时候并没有去执行reduce,而是直接返回了,导致reduce里面吧ip加入r.ipCount 这个object集合里面的语句不执行,所以才需要有红颜色的这句话。

以上是我在写mapreduce的一些经验总结,根据我的测试结果上面的修正是正确的,而且很容易范这个错误,如果上面的说法有值得质疑的地方,欢迎回帖讨论。

 

加载中
1
观指居士
观指居士

MongoDB的Mapreduce确实会在map针对map之后只有一个的emit结果跳过reduce,这在Mapreduce那节的文档中有写。所以map的时候应该就把结果需要的数据结构和计算考虑进去。

另外看代码,是考虑根据ip做统计,那么对ip为空或者实际情况下无法单算的,应该处理成某种情况,那么把ip作为key的一部分写进去。

function Map() {
    //根据实际应用,是否还需要把“”等等情况归结为某一类,或某几类特殊ip处理计数 
  var key={ip:(this.ip?this.ip:"null"),other_key_items:1}; 

  emit(key, {count:1, progress: this.progress, ip:this.ip}) ; 
}

 
function Reduce(key, emits) { 
  // 最好定义同map中emit的结果,保证最终结果结构一致 
  var r = {count:0, progress:0,ip:""};

  foreach(var i in emits){

    r.count+= emits[i].count;

    //这里叠加总数,作为平均数计算的中间结果
    r.progress+= emits[i].count* emits[i].progress; 

    //保证同map只有一个结果的跳过reduce之后的最终结果一致
    r.ip= emits[i].ip;  

  }

  r.progress= r.progress/r.count;

  return r;

} 

/*

//这里finalize应该用不上了

function Finalize(key, reduced) { 
}

*/

/*

最终结果可以预测所有结果数据结构为

{

  "key":{"ip":"...",...},

  "value":{

    "count":1, 

    ”progress":123.12, 

    "ip":"127.0.0.1"

}

}

*/




0
wankaiming
wankaiming

楼主在公司里面使用到上述功能吗???

0
FLZYUP
FLZYUP

引用来自“网网”的答案

楼主在公司里面使用到上述功能吗???

恩 是啊 我们已经在生产线上用了 刚才的问题是做统计的时候用到的
0
w
wangzs
楼主好,我公司也在用mongodb,也在mapreduce的时候发生了你说的那个reduce执行多次的情况 ,但我发现生的情况是,是通过map映射出来的vals发现了内嵌数组:比如,正确返回应该是[1,2,3,4,5,6,7,8],但在真实情况中,返回的却是[[1,2,3,4],5,6,7,8],所以导致我在计算length的时候,发现数量变少了,请问这种情况要怎么处理,谢谢
c
coolcao
计算values的长度的确有点问题,但你是怎么知道返回的是内嵌数组的形式,怎么将values打印出来看看呢?
b
berserker527
你好 那个内嵌数组的问题 你解决了吗 我也困扰了好几天
0
Raynor1
Raynor1

好几个地方都写错了。if(var x in xxxx) 这里是for 

reduce 下面少一个return

0
colalife
colalife
return {count:values.length};
返回顶部
顶部