`
jorwen_fang
  • 浏览: 50690 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

MongoDB学习笔记(七):聚合MapReduce

阅读更多

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

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

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

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

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

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

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

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

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函数:

01.> map = function(){
02....         for(var key in this){
03....             emit(key, {"count" : 1});
04....         }
05.... };
06.function () {
07.for (var key in this) {
08.emit(key, {count:1});
09.}
10.}
11.>

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

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

01.> reduce = function(key, emits){
02....            var total = 0;
03....            for(var i in emits) {
04....                total += emits.count;
05....            }
06....            return {"count" : total};
07....        };
08.function (key, emits) {
09.var total = 0;
10.for (var i in emits) {
11.total += emits.count;
12.}
13.return {count:total};
14.}
15.>

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

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

01.> mr = db.runCommand({"mapreduce" : "testcol", "map" : map, "reduce" : reduce, "out" :"testcolColumns"});
02.{
03."result" : "testcolColumns",
04."timeMillis" : 93,
05."counts" : {
06."input" : 4,
07."emit" : 11,
08."reduce" : 3,
09."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” : 最终在目标集合中生成的文档数量 

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

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添加一些标签,作为主题,其他用户可以为这条信息打分。我们有一个集合,收集了这些信息,然后我们需要看看哪种主题最为热门,热门程度由最新打分日期和所给分数共同决定,我们先看一下这个集合:

01.> db.urlvote.find();
02.{ "_id" : ObjectId("502767f1e02ab93d5c5be7a6"), "date" : ISODate("2012-08-12T08:23:13.292Z"), "score": 10, "tags" : [ "it tech", "program" ], "url" :
03."www.csdn.net" }
04.{ "_id" : ObjectId("50276810e02ab93d5c5be7a7"), "date" : ISODate("2012-08-12T08:23:44.836Z"), "score": 3, "tags" : [ "search engine" ], "url" : "www.
05.baidu.com" }
06.{ "_id" : ObjectId("5027683be02ab93d5c5be7a8"), "date" : ISODate("2012-08-12T08:24:27.392Z"), "score": 5, "tags" : [ "search engine", "news" ], "url"
07.: "www.sina.com.cn" }
08.{ "_id" : ObjectId("5027685de02ab93d5c5be7a9"), "date" : ISODate("2012-08-12T08:25:01.588Z"), "score": 8, "tags" : [ "it tech", "java" ], "url" : "ww
09.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函数为:

01.> map = function(){
02....     for(var i in this.tags){
03....         var recency = 1/(new Date() - this.date);
04....         var score = recency * this.score;
05....         emit(this.tags, {"urls":[this.url], "score":this.score});
06....     }
07.... };
08.function () {
09.for (var i in this.tags) {
10.var recency = 1 / (new Date - this.date);
11.var score = recency * this.score;
12.emit(this.tags, {urls:[this.url], score:this.score});
13.}
14.}

reduce函数为:

01.> reduce = function(key, emits) {
02....     var total = {"urls":[], "score":0};
03....     for(var i in emits) {
04....         emits.urls.forEach(function(url) {
05....             total.urls.push(url);
06....         });
07....         total.score += emits.score;
08....     }
09....     return total;
10.... };
11.function (key, emits) {
12.var total = {urls:[], score:0};
13.for (var i in emits) {
14.emits.urls.forEach(function (url) {total.urls.push(url);});
15.total.score += emits.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过程:

01.> db.runCommand({"mapreduce":"urlvote", "map":map, "reduce":reduce, "out":"urlvoteresult"});
02.{
03."result" : "urlvoteresult",
04."timeMillis" : 0,
05."counts" : {
06."input" : 5,
07."emit" : 8,
08."reduce" : 2,
09."output" : 5
10.},
11."ok" : 1
12.}

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

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个键!

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics