您的位置:首页 > 数据库 > Mongodb

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

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

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

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

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

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

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

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

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

> db.testcol.find();
{ "_id" : ObjectId("501fadbee64fb552a4f6651e"), "x" : 1 }
{ "_id" : ObjectId("50275d56e02ab93d5c5be7a3"), "name" : "abc", "things" : [ "plane", "gun" ] }
{ "_id" : ObjectId("50275d6be02ab93d5c5be7a4"), "name" : "ddd", "weapon" : "bomb" }
{ "_id" : ObjectId("50275d7ce02ab93d5c5be7a5"), "nickname" : "viper", "weapon" : "knife" }
>

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

> map = function(){
...         for(var key in this){
...             emit(key, {"count" : 1});
...         }
... };
function () {
for (var key in this) {
emit(key, {count:1});
}
}
>

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

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

> reduce = function(key, emits){
...            var total = 0;
...            for(var i in emits) {
...                total += emits[i].count;
...            }
...            return {"count" : total};
...        };
function (key, emits) {
var total = 0;
for (var i in emits) {
total += emits[i].count;
}
return {count:total};
}
>

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

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

> mr = db.runCommand({"mapreduce" : "testcol", "map" : map, "reduce" : reduce, "out" : "testcolColumns"});
{
"result" : "testcolColumns",
"timeMillis" : 93,
"counts" : {
"input" : 4,
"emit" : 11,
"reduce" : 3,
"output" : 6
},
"ok" : 1
}
>

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

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

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

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

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

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

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

> db.testcolColumns.find();
{ "_id" : "_id", "value" : { "count" : 4 } }
{ "_id" : "name", "value" : { "count" : 2 } }
{ "_id" : "nickname", "value" : { "count" : 1 } }
{ "_id" : "things", "value" : { "count" : 1 } }
{ "_id" : "weapon", "value" : { "count" : 2 } }
{ "_id" : "x", "value" : { "count" : 1 } }
>

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

【例:网页分类】

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

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


我们的map函数为:

> map = function(){
...     for(var i in this.tags){
...         var recency = 1/(new Date() - this.date);
...         var score = recency * this.score;
...         emit(this.tags[i], {"urls":[this.url], "score":this.score});
...     }
... };
function () {
for (var i in this.tags) {
var recency = 1 / (new Date - this.date);
var score = recency * this.score;
emit(this.tags[i], {urls:[this.url], score:this.score});
}
}


reduce函数为:

> reduce = function(key, emits) {
...     var total = {"urls":[], "score":0};
...     for(var i in emits) {
...         emits[i].urls.forEach(function(url) {
...             total.urls.push(url);
...         });
...         total.score += emits[i].score;
...     }
...     return total;
... };
function (key, emits) {
var total = {urls:[], score:0};
for (var i in emits) {
emits[i].urls.forEach(function (url) {total.urls.push(url);});
total.score += emits[i].score;
}
return total;
}
>

在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过程:

> db.runCommand({"mapreduce":"urlvote", "map":map, "reduce":reduce, "out":"urlvoteresult"});
{
"result" : "urlvoteresult",
"timeMillis" : 0,
"counts" : {
"input" : 5,
"emit" : 8,
"reduce" : 2,
"output" : 5
},
"ok" : 1
}


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

> db.urlvoteresult.find();
{ "_id" : "it tech", "value" : { "urls" : [ "www.csdn.net", "www.javaeye.com" ], "score" : 18 } }
{ "_id" : "java", "value" : { "urls" : [ "www.javaeye.com" ], "score" : 8 } }
{ "_id" : "news", "value" : { "urls" : [ "www.sina.com.cn" ], "score" : 5 } }
{ "_id" : "program", "value" : { "urls" : [ "www.csdn.net" ], "score" : 10 } }
{ "_id" : "search engine", "value" : { "urls" : [ "www.baidu.com", "www.sina.com.cn", "www.google.com" ], "score" : 18 } }
>


以上就是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个键!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: