当前访客身份:游客 [ 登录  | 注册加入尚学堂]
直播
新闻资讯

在MongoDB的MapReduce上踩过的坑

我来了! 发表于 2年前  | 评论(0 )| 阅读次数(834 )|   0 人收藏此文章,   我要收藏

  太久没动这里,目前人生处于一个新的开始。这次博客的内容很久前就想更新上来,但是一直没找到合适的时间点(哈哈,其实就是懒),主要内容集中在使用Mongodb时的一些隐蔽的MapReduce问题:

  1、Reduce时的计数问题

  2、Reduce时的提取数据问题

  另外,补充一个小tips:mongoDB中建立的索引,优先使用固定的,而不要使用范围。

 

一、MapReduce时的计数问题

   这个问题主要出现在使用“+1”的思路去计算累计次数时。如果在Map后的某一类中,记录量过大,就会导致计数失败。

  具体演示如下:

  原始数据(有400条一样的存在数据库results表中):{ "grade" : 1, "name" : "lekko", "score" : 95 }   

  进行MapReduce:

 
db.runCommand({ mapreduce: "results", 
 map : function Map() {
    emit(
        {grade:this.grade},
        {recnum:1,score:this.score}
    );
},
 reduce : function Reduce(key, values) {
    var reduced = {recnum:0,score:0};
    values.forEach(function(val){
            reduced.score += val.score;
            ++reduced.recnum;
        });
    return reduced;
},
finalize : function Finalize(key, reduced) { 
        return reduced;
},
out : { inline : 1 }
});



  满怀希望地以为value.recnum会输出400,结果却是101!而value.scorce却是输出的正确的:38000(95*400)。本人在这疑惑了好久,并且通过更改reduce函数: function Reduce(key, values) { return {test:values}; } ,发现数据是这样的:

  在原本Reduce函数中的forEach只遍历了第一层的数据,即101个,所以++操作也只做了101次!

  经过思考,导致问题的原因关键就在于MapReduce中emit后的Bosn的数据格式,一个大于100的Array,会被拆分存储,变成了非线性的链表结构,如图:

  那么,分数相加却能正确,可以大胆地推测:“reduced.score += val.score;” 语句可以智能地找到所有子结点的score并相加!

 
function Reduce(key, values) {    ;
    var reduced = {recnum:0,score:0};
    values.forEach(function(val){
            reduced.score += val.score;
            reduced.recnum += val.recnum;
        });
    return reduced;
}




二、在Reduce中把数据提取出来组成Array


  这个问题产生的原因与上面的相似,也是由于emit后的数据在reduce时是非线性的(有层次关系),所以提取数据字段时也会产生问题,为了测试,往上面所说的表中再插入3条数据:

   { "grade" : 1, "name" : "monkey", "score" : 95 }, { "grade" : 2, "name" : "sudan", "score" : 95 }, { "grade" : 2, "name" : "xiaoyan", "score" : 95 } 

  编写提取出各个grade的所有人名(不重复)列表:

 
db.runCommand({ mapreduce: "results", 
 map : function Map() {
    emit(
        {grade:this.grade},
        {name:this.name}
    ); 
},
 reduce : function Reduce(key, values) {
    var reduced = {names:[]};
    values.forEach(function(val) {
        var isExist = false;
        for(var i = 0; i<reduced.names.length; i++) {
            var cur = reduced.names[i];
            if(cur==val.name){
                isExist = true;
                break;
            }
        }
        if(!isExist)
            reduced.names.push(val.name);
    });
    return reduced;
},
 finalize : function Finalize(key, reduced) {
    return reduced;
},
 out : { inline : 1 }
 });



  返回结果为:

1  { "_id" : {"grade" : 1},
2    "value" :{ "names" : [null,"lekko"]}
3  },
4  { "_id" : {"grade" : 2},
5    "value" :{ "names" : ["xiaoyan","sudan"]}
6  }



   新插入的grade=2的两条数据正常了,但grade=1的monkey却不见了!采用问题一的思维方式,肯定也是在Reduce时遍历到一个数组对象,其name值为空,也给添加进来了,monkey对象根本就没有访问到。

  解决这一问题的方法是,抛弃MapReduce,改用Group:

 
db.results.group({
 key : {"grade":true}, 
 initial : {names:[]}, 
 reduce : function Reduce(val, out) {
    var isExist = false;
    for(var i = 0; i<out.names.length; i++) {
        var cur = out.names[i];
        if(cur==val.name){
            isExist = true;
            break;
        }
    }
    if(!isExist)
        out.names.push(val.name);    
}, 
 finalize : function Finalize(out) {
    return out;
}});



  这样,便可正常取到grade=1时的name非重复集合!虽说MapReduce比Group要强大,速度也要快很多,但像这种要从大量项 (超过100条)中提取数据,就有很大风险了。所以,使用MapReduce时,尽量只用到累加、累减、累乘等基本操作,不要去用++、push、 delete等可能会产生风险的操作!

 

三、补充几个小Tips

  1、使用Group或MapReduce时,如果一个分类只有一个元素,那么Reduce函数将不会执行,但Finalize函数还是会执行 的。这时你要在Finalize函数中考虑一个元素与多个元素返回结果的一致性(比如,你把问题二中插入一个grade=3的数据看看,执行返回的 grade=3时还有names集合吗?)。

  2、查找范围时的索引效率,如果查询的是一个值的范围,它索引的优先级是很低的。比如一个表test,有海量元素,字段有 'committime'、'author',建立了两个索引:author_1、committime:-1,author:1,下面的测试证明了效 率:

    db.test.find({'committime': {'$gt':910713600000,'$lte':1410192000000},'author':'lekko'}).hint({committime:- 1,author:1}).explain()   "millis" : 49163
db.test.find({'committime': {'$gt':910713600000,'$lte':1410192000000},'author':'lekko'}).explain()  author_1                  "millis" : 2641

原址:http://www.cnblogs.com/lekko/p/3963418.html

分享到:0
关注微信,跟着我们扩展技术视野。每天推送IT新技术文章,每周聚焦一门新技术。微信二维码如下:
微信公众账号:尚学堂(微信号:bjsxt-java)
声明:博客文章版权属于原创作者,受法律保护。如果侵犯了您的权利,请联系管理员,我们将及时删除!
(邮箱:webmaster#sxt.cn(#换为@))
北京总部地址:北京市海淀区西三旗桥东建材城西路85号神州科技园B座三层尚学堂 咨询电话:400-009-1906 010-56233821
Copyright 2007-2015 北京尚学堂科技有限公司 京ICP备13018289号-1 京公网安备11010802015183