学习MongoDB--(6-2):聚合(MapReduce使用)

MapReduce是聚合工具的明星!前面讨论的count、distinct、group能做到的,MapReduce都可以做!他是一个可以轻松并行化到多台服务器的聚合方法!他会拆分问题,将各个部分发送到不同的机器上执行,当所有机器都完成时,再把结果汇集起来形成最终完整的结果!

MapReduce在MongoDB中的使用通常有如下几个步骤:

1》 映射(map),将操作映射到集合中的每一个文档,这个操作在文档上执行后,要么没产生任何结果,要么产生一些键值对!

2》 洗牌(shuffle), 这是一个中间过程。上述映射会产生一些键值对,这个动作会将这些键值对按键分组,并将值组成列表设置到对应的键中。

3》 化简(reduce),把上述操作产生的值为列表的键值对化简为一个单值!这个键值对会被返回,他有可能还会参与下一轮的洗牌,化简操作。直到每个键的列表只有一个值为止。

使用MapReduce的代价是速度,group操作不是很快,MapReduce更慢!不能把MapReduce应用于实时系统中!要作为后台任务来运行MapReduce,其运行完毕后,会将结果保存在一个集合中,我们后期可以对这个结果集合进行实时操作!MapReduce比较复杂,我们通过几个例子先看看具体用法:

【例:找出集合中所有的键】

MongoDB没有模式,因此无法通过一个文档得知这个集合有多少个键,这里我们利用MapReduce来统计一个集合中键的个数和每个键出现的次数(这里没有考虑内嵌文档的键,可以通过调整map函数实现)。我们先看看我们需要统计的集合,每个文档中键都不一致(这里只是为了测试,实际中不要设计这样的集合):

[javascript]  view plain  copy
  1. > db.testcol.find();  
  2. "_id" : ObjectId("501fadbee64fb552a4f6651e"), "x" : 1 }  
  3. "_id" : ObjectId("50275d56e02ab93d5c5be7a3"), "name" : "abc""things" : [ "plane""gun" ] }  
  4. "_id" : ObjectId("50275d6be02ab93d5c5be7a4"), "name" : "ddd""weapon" : "bomb" }  
  5. "_id" : ObjectId("50275d7ce02ab93d5c5be7a5"), "nickname" : "viper""weapon" : "knife" }  
  6. >  

定义映射环节(map)所需map函数:

[javascript]  view plain  copy
  1. > map = function(){  
  2. ...         for(var key in this){  
  3. ...             emit(key, {"count" : 1});  
  4. ...         }  
  5. ... };  
  6. function () {  
  7.     for (var key in this) {  
  8.         emit(key, {count:1});  
  9.     }  
  10. }  
  11. >  

map函数使用函数emit(系统提供)“返回”要处理的值,这里用emit将文档的某个键的计数返回({“count” : 1})。我们这里需要为每个键单独计数,所以要为每个键分别调用一次emit。this代表目前进入map函数中的文档!

定义化简环节(reduce)所需reduce函数:

[javascript]  view plain  copy
  1. > reduce = function(key, emits){  
  2. ...            var total = 0;  
  3. ...            for(var i in emits) {  
  4. ...                total += emits[i].count;  
  5. ...            }  
  6. ...            return {"count" : total};  
  7. ...        };  
  8. function (key, emits) {  
  9.     var total = 0;  
  10.     for (var i in emits) {  
  11.         total += emits[i].count;  
  12.     }  
  13.     return {count:total};  
  14. }  
  15. >  

通过map函数会产生很多{"count" : 1}这样的文档,且每一个与一个键关联!这种由一个或多个{“count” :1}文档组成的数组(由洗牌阶段生成),会传递给reduce函数,作为reduce函数的第二个参数!reduce函数要能够被反复调用,因此reduce函数返回的值必须可以作为其第二个参数的一个元素!如上例!

运行数据库命令,调用MapReduce过程:

[javascript]  view plain  copy
  1. > mr = db.runCommand({"mapreduce" : "testcol""map" : map, "reduce" : reduce, "out" : "testcolColumns"});  
  2. {  
  3.         "result" : "testcolColumns",  
  4.         "timeMillis" : 93,  
  5.         "counts" : {  
  6.                 "input" : 4,  
  7.                 "emit" : 11,  
  8.                 "reduce" : 3,  
  9.                 "output" : 6  
  10.         },  
  11.         "ok" : 1  
  12. }  
  13. >  

运行命令时, 键“mapreduce” 指定集合名称,键“map”指定映射函数, 键“reduce”指定化简函数,“out”指定最后输出的集合名称!在我所使用2.0.6版本的MongoDB,键“out”必须指明!

我们运行后,返回的文档,其中键“counts”为一个内嵌文档,我们先说一下这个内嵌文档中各个键的含义:

1》 “input” : 在整个过程发送到“map”函数的文档个数,即“map”函数执行的次数

2》 “emit” : 在整个过程,“emit”函数执行的次数

3》 “reduce” : 在整个过程,“reduce”函数执行的次数

4》 “output” : 最终在目标集合中生成的文档数量

我们查看一下最终生成的目标集合:

[javascript]  view plain  copy
  1. > db.testcolColumns.find();  
  2. "_id" : "_id""value" : { "count" : 4 } }  
  3. "_id" : "name""value" : { "count" : 2 } }  
  4. "_id" : "nickname""value" : { "count" : 1 } }  
  5. "_id" : "things""value" : { "count" : 1 } }  
  6. "_id" : "weapon""value" : { "count" : 2 } }  
  7. "_id" : "x""value" : { "count" : 1 } }  
  8. >  

其中,键“_id”的值为原集合中键的名称,键“value” 指明这个键在原集合中出现的次数!

【例:网页分类】

我们有这样一个网站,用户可以在其上提交他们喜爱的链接url,并且提交者可以为这个url添加一些标签,作为主题,其他用户可以为这条信息打分。我们有一个集合,收集了这些信息,然后我们需要看看哪种主题最为热门,热门程度由最新打分日期和所给分数共同决定,我们先看一下这个集合:

[javascript]  view plain  copy
  1. > db.urlvote.find();  
  2. "_id" : ObjectId("502767f1e02ab93d5c5be7a6"), "date" : ISODate("2012-08-12T08:23:13.292Z"), "score" : 10, "tags" : [ "it tech""program" ], "url" :  
  3.  "www.csdn.net" }  
  4. "_id" : ObjectId("50276810e02ab93d5c5be7a7"), "date" : ISODate("2012-08-12T08:23:44.836Z"), "score" : 3, "tags" : [ "search engine" ], "url" : "www.  
  5. baidu.com" }  
  6. "_id" : ObjectId("5027683be02ab93d5c5be7a8"), "date" : ISODate("2012-08-12T08:24:27.392Z"), "score" : 5, "tags" : [ "search engine""news" ], "url"  
  7.  : "www.sina.com.cn" }  
  8. "_id" : ObjectId("5027685de02ab93d5c5be7a9"), "date" : ISODate("2012-08-12T08:25:01.588Z"), "score" : 8, "tags" : [ "it tech""java" ], "url" : "ww  
  9. w.javaeye.com" }  
  10. "_id" : ObjectId("5027687ae02ab93d5c5be7aa"), "date" : ISODate("2012-08-12T08:25:30.354Z"), "score" : 10, "tags" : [ "search engine" ], "url" : "www  
  11. .google.com" }  
  12. >  

我们的map函数为:

[javascript]  view plain  copy
  1. > map = function(){  
  2. ...     for(var i in this.tags){  
  3. ...         var recency = 1/(new Date() - this.date);  
  4. ...         var score = recency * this.score;  
  5. ...         emit(this.tags[i], {"urls":[this.url], "score":this.score});  
  6. ...     }  
  7. ... };  
  8. function () {  
  9.     for (var i in this.tags) {  
  10.         var recency = 1 / (new Date - this.date);  
  11.         var score = recency * this.score;  
  12.         emit(this.tags[i], {urls:[this.url], score:this.score});  
  13.     }  
  14. }  

reduce函数为:

[javascript]  view plain  copy
  1. > reduce = function(key, emits) {  
  2. ...     var total = {"urls":[], "score":0};  
  3. ...     for(var i in emits) {  
  4. ...         emits[i].urls.forEach(function(url) {  
  5. ...             total.urls.push(url);  
  6. ...         });  
  7. ...         total.score += emits[i].score;  
  8. ...     }  
  9. ...     return total;  
  10. ... };  
  11. function (key, emits) {  
  12.     var total = {urls:[], score:0};  
  13.     for (var i in emits) {  
  14.         emits[i].urls.forEach(function (url) {total.urls.push(url);});  
  15.         total.score += emits[i].score;  
  16.     }  
  17.     return total;  
  18. }  
  19. >  

在reduce函数中,我们涉及到了数组的几个方法,forEach(function)和push,这都是javascript中提供的,可以学着用一下!再稍微说一下,javascript中通过for去遍历数组for(var i in array),这个i是数组的索引,从0开始,通过for去遍历一个json对象({key1:val1,key2:val2 .....}),for(var i in jsonobj),这个i是json对象中的键!

开始执行MapReduce过程:

[javascript]  view plain  copy
  1. > db.runCommand({"mapreduce":"urlvote""map":map, "reduce":reduce, "out":"urlvoteresult"});  
  2. {  
  3.         "result" : "urlvoteresult",  
  4.         "timeMillis" : 0,  
  5.         "counts" : {  
  6.                 "input" : 5,  
  7.                 "emit" : 8,  
  8.                 "reduce" : 2,  
  9.                 "output" : 5  
  10.         },  
  11.         "ok" : 1  
  12. }  


查看结果集合中的文档为:

[javascript]  view plain  copy
  1. > db.urlvoteresult.find();  
  2. "_id" : "it tech""value" : { "urls" : [ "www.csdn.net""www.javaeye.com" ], "score" : 18 } }  
  3. "_id" : "java""value" : { "urls" : [ "www.javaeye.com" ], "score" : 8 } }  
  4. "_id" : "news""value" : { "urls" : [ "www.sina.com.cn" ], "score" : 5 } }  
  5. "_id" : "program""value" : { "urls" : [ "www.csdn.net" ], "score" : 10 } }  
  6. "_id" : "search engine""value" : { "urls" : [ "www.baidu.com""www.sina.com.cn""www.google.com" ], "score" : 18 } }  
  7. >  


以上就是MapReduce在MongoDB中的两个例子,使用MapReduce的关键还是在于知道哪些情况适合于这种方式去解决,并且知道如何定义map和reduce函数。

上面我们在运行mapreduce命令时,只是涉及了"mapreduce" ,“map”,“reduce”,“out”键,我们还有如下键可用:

1》 “verbose” 布尔,是否产生详细的服务器端日志

2》 “query” 文档,在将集合中的文档发往map函数前,先用这个对文档进行过滤

3》 “sort” 文档, 在讲集合中的文档发往map函数前,先对文档排序,可与“limit”结合使用

4》 “limit” 整数, 取集合前部多少个文档发往map函数

后3者,可以减少发往map函数的文档数量,这个可以提升MapReduce的效率!如果我们事先确定只需对部分文档进行MapReduce操作,我们要果断使用这3个键!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值