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

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

2017-09-16 09:27 501 查看
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

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

[javascript] view
plain copy

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

[javascript] view
plain copy

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

[javascript] view
plain copy

> 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” : 最终在目标集合中生成的文档数量

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

[javascript] view
plain copy

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

[javascript] view
plain copy

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

[javascript] view
plain copy

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

[javascript] view
plain copy

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

[javascript] view
plain copy

> 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  

}  

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

[javascript] view
plain copy

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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: