MongoDB中的MapReduce简要介绍_MongoDB_脚本之家

作者:计算机专家

前言

MongoDB虽然不像我们常用的mysql,sqlserver,oracle等关系型数据库有group by函数那样方便分组,但是MongoDB要实现分组也有3个办法:

MongoDB MapReduce

玩过Hadoop的小伙伴对MapReduce应该不陌生,MapReduce的强大且灵活,它可以将一个大问题拆分为多个小问题,将各个小问题发送到不同的机器上去处理,所有的机器都完成计算后,再将计算结果合并为一个完整的解决方案,这就是所谓的分布式计算。本文我们就来看看MongoDB中MapReduce的使用。

 * Mongodb三种分组方式:

MapReduce是一种计算模型,简单的说就是将大批量的工作执行,然后再将结果合并成最终结果。这样做的好处是可以在任务被分解后,可以通过大量机器进行并行计算,减少整个操作的时间。

打算用mongodb mapreduce之前一定要知道的事!!!

 * 1、group(先筛选再分组,不支持分片,对数据量有所限制,效率不高)

上面是MapReduce的理论部分,下面说实际的应用,下面以MongoDB MapReduce为例说明。

mapreduce其实是分批处理数据的,每一百次重新reduce处理,所以到reduce里的数据如果是101条,那就会分2次进入。

 * 2、mapreduce(基于js引擎,单线程执行,效率较低,适合用做后台统计等)

下面是MongoDB官方的一个例子:复制代码 代码如下:> db.things.insert( { _id : 1, tags : ['dog', 'cat'] } );> db.things.insert( { _id : 2, tags : ['cat'] } );> db.things.insert( { _id : 3, tags : ['mouse', 'cat', 'dog'] } );> db.things.insert( { _id : 4, tags : [] } );

这导致的问题就是在reduce中 如果 初始化 var count = 0;在循环中 count ++,最后输出的是1???

 * 3、aggregate(推荐) (如果你的PHP的mongodb驱动版本需>=1.3.0,推荐你使用aggregate,性能要高很多,并且使用上要简单些,不过1.3的目前还不支持账户认证模式,可以通过 

> // map function> map = function(){... this.tags.forEach{... emit;... }... );...};

避免都方法是,把数据存在返回的value里,这个value是会在循环进入reduce的时候重用的。在循环中 count += value.count就能把之前都100加上了!!!

下面就来看下mapreduce方式:

> // reduce function> reduce = function{... var total = 0;... for ( var i=0; i

还有如果只有一条数据,那它不会进入reduce,会直接返回。

 

db.things.mapReduce(map,reduce,{out:'tmp'}){ "result" : "tmp", "timeMillis" : 316, "counts" : { "input" : 4, "emit" : 6, "output" : 3 }, "ok" : 1,}> db.tmp.find(){ "_id" : "cat", "value" : { "count" : 3 } }{ "_id" : "dog", "value" : { "count" : 2 } }{ "_id" : "mouse", "value" : { "count" : 1 } }

下面是具体例子:

Mongodb官网对MapReduce介绍:

例子很简单,计算一个标签系统中每个标签出现的次数。

string map = @"function() {var view = this;emit(view.activity, {pv: 1});}";string reduce = @" function {var result = {pv: 0};values.forEach{ result.pv += value.pv;});return result;}";string finalize = @"function{return value;}";

Map/reduce in MongoDB is useful for batch processing of data and aggregation operations. It is similar in spirit to using something like Hadoop with all input coming from a collection and output going to a collection. Often, in a situation where you would have used GROUP BY in SQL, map/reduce is the right tool in MongoDB.

这里面,除了emit函数之外,所有都是标准的js语法,这个emit函数是非常重要的,可以这样理解,当所有需要计算的文档(因为在mapReduce时,可以对文档进行过滤,接下来会讲到)执行完了map函数,map函数会返回key_values对,key即是emit中的第一个参数key,values是对应同一key的emit的n个第二个参数组成的数组。这个key_values会作为参数传递给reduce,分别作为第1.2个参数。

mapReduce

大致意思是:Mongodb中的Map/reduce主要是用来对数据进行批量处理和聚合操作,有点类似于使用Hadoop对集合数据进行处理,所有输入数据都是从集合中获取,而MapReduce后输出的数据也都会写入到集合中。通常类似于我们在SQL中使用Group By语句一样。
使用MapReduce要实现两个函数:Map和Reduce。Map函数调用emit(key,value)遍历集合中所有的记录,将key与value传给Reduce函数进行处理。Map函数和Reduce函数是使用Javascript编写的,并可以通过db.runCommand或mapreduce命令来执行MapReduce操作。

reduce函数的任务就是将key-values变成key-value,也就是把values数组变成一个单一的值value。当key-values中的values数组过大时,会被再切分成很多个小的key-values块,然后分别执行Reduce函数,再将多个块的结果组合成一个新的数组,作为Reduce函数的第二个参数,继续Reducer操作。可以预见,如果我们初始的values非常大,可能还会对第一次分块计算后组成的集合再次Reduce。这就类似于多阶的归并排序了。具体会有多少重,就看数据量了。

MongoDB中的MapReduce可以用来实现更复杂的聚合命令,使用MapReduce主要实现两个函数:map函数和reduce函数,map函数用来生成键值对序列,map函数的结果作为reduce函数的参数,reduce函数中再做进一步的统计,比如我的数据集如下:

 

reduce一定要能被反复调用,不论是映射环节还是前一个简化环节。所以reduce返回的文档必须能作为reduce的第二个参数的一个元素。

{"_id" : ObjectId("59fa71d71fd59c3b2cd908d7"),"name" : "鲁迅","book" : "呐喊","price" : 38.0,"publisher" : "人民文学出版社"}{"_id" : ObjectId("59fa71d71fd59c3b2cd908d8"),"name" : "曹雪芹","book" : "红楼梦","price" : 22.0,"publisher" : "人民文学出版社"}{"_id" : ObjectId("59fa71d71fd59c3b2cd908d9"),"name" : "钱钟书","book" : "宋诗选注","price" : 99.0,"publisher" : "人民文学出版社"}{"_id" : ObjectId("59fa71d71fd59c3b2cd908da"),"name" : "钱钟书","book" : "谈艺录","price" : 66.0,"publisher" : "三联书店"}{"_id" : ObjectId("59fa71d71fd59c3b2cd908db"),"name" : "鲁迅","book" : "彷徨","price" : 55.0,"publisher" : "花城出版社"}

MapReduce命令如下:
[javascript]
db.runCommand( 
{ mapreduce : <collection>, 
   map : <mapfunction>, 
   reduce : <reducefunction> 
   [, query : <query filter object>] 
   [, sort : <sort the query.  useful for optimization>] 
   [, limit : <number of objects to return from collection>] 
   [, out : <output-collection name>] 
   [, keeptemp: <true|false>] 
   [, finalize : <finalizefunction>] 
   [, scope : <object where fields go into javascript global scope >] 
   [, verbose : true] 
 } 
); 

(当书写Map函数时,emit的第二个参数组成数组成了reduce函数的第二个参数,而Reduce函数的返回值,跟emit函数的第二个参数形式要一致,多个reduce函数的返回值可能会组成数组作为新的第二个输入参数再次执行Reduce操作。)

假如我想查询每位作者所出的书的总价,操作如下:

db.runCommand(
{ mapreduce : <collection>,
   map : <mapfunction>,
   reduce : <reducefunction>
   [, query : <query filter object>]
   [, sort : <sort the query.  useful for optimization>]
   [, limit : <number of objects to return from collection>]
   [, out : <output-collection name>]
   [, keeptemp: <true|false>]
   [, finalize : <finalizefunction>]
   [, scope : <object where fields go into javascript global scope >]
   [, verbose : true]
 }
);

MapReduce函数的参数列表如下:复制代码 代码如下:db.runCommand({ mapreduce : , map : , reduce : [, query : ] [, sort : ] [, limit : ] [, out : ] [, keeptemp: ] [, finalize : ] [, scope : ] [, verbose : true]});或者这么写:复制代码 代码如下:db.collection.mapReduce( , , { , , , , , , , , } )

var map=function(){emit}var reduce=function{return Array.sum}var options={out:"totalPrice"}db.sang_books.mapReduce;db.totalPrice.find()

参数说明:

1.mapreduce:指定要进行mapreduce处理的collection2.map:map函数3.reduce:reduce函数4.out:输出结果的collection的名字,不指定会默认创建一个随机名字的collection(如果使用了out选项,就不必指定keeptemp:true了,因为已经隐含在其中了)5.query:一个筛选条件,只有满足条件的文档才会调用map函数。(query。limit,sort可以随意组合)6.sort:和limit结合的sort排序参数,可以优化分组机制7.limit:发往map函数的文档数量的上限(要是没有limit,单独使用sort的用处不大)8.keytemp:true或false,表明结果输出到的collection是否是临时的,如果想在连接关闭后仍然保留这个集合,就要指定keeptemp为true,如果你用的是MongoDB的mongo客户端连接,那必须exit后才会删除。如果是脚本执行,脚本退出或调用close会自动删除结果collection9.finalize:是函数,它会在执行完map、reduce后再对key和value进行一次计算并返回一个最终结果,这是处理过程的最后一步,所以finalize就是一个计算平均数,剪裁数组,清除多余信息的恰当时机10.scope:javascript代码中要用到的变量,在这里定义的变量在map,reduce,finalize函数中可见11.verbose:用于调试的详细输出选项,如果想看MpaReduce的运行过程,可以设置其为true。也可以print把map,reduce,finalize过程中的信息输出到服务器日志上。

emit函数主要用来实现分组,接收两个参数,第一个参数表示分组的字段,第二个参数表示要统计的数据,reduce来做具体的数据处理操作,接收两个参数,对应emit方法的两个参数,这里使用了Array中的sum函数对price字段进行自加处理,options中定义了将结果输出的集合,届时我们将在这个集合中去查询数据,默认情况下,这个集合即使在数据库重启后也会保留,并且保留集合中的数据。

mapreduce:要操作的目标集合

执行MapReduce函数返回的文档结构如下:复制代码 代码如下: { result : ,

{ "_id" : "曹雪芹", "value" : 22.0}{ "_id" : "钱钟书", "value" : 165.0}{ "_id" : "鲁迅", "value" : 93.0}

map:映射函数(生成键值对序列,作为Reduce函数的参数)

timeMillis : ,

再比如我想查询每位作者出了几本书,如下:

reduce:统计函数

counts : {

var map=function}var reduce=function{return Array.sum}var options={out:"bookNum"}db.sang_books.mapReduce;db.bookNum.find()

{ "_id" : "曹雪芹", "value" : 1.0}{ "_id" : "钱钟书", "value" : 2.0}{ "_id" : "鲁迅", "value" : 2.0}

query:目标记录过滤

input : ,

将每位作者的书列出来,如下:

sort:对目标记录排序

emit : ,

var map=function(){emit}var reduce=function{return value.join}var options={out:"books"}db.sang_books.mapReduce;db.books.find()

{ "_id" : "曹雪芹", "value" : "红楼梦"}{ "_id" : "钱钟书", "value" : "宋诗选注,谈艺录"}{ "_id" : "鲁迅", "value" : "呐喊,彷徨"}

limit:限制目标记录数量

output :

比如查询每个人售价在¥40以上的书:

out:统计结果存放集合(如果不指定则使用临时集合,在客户端断开后自动删除)

ok : <1_if_ok>,

var map=function(){emit}var reduce=function{return value.join}var options={query:{price:{$gt:40}},out:"books"}db.sang_books.mapReduce;db.books.find()

keeptemp:是否保留临时集合

[, err : ]

本文由杏彩发布,转载请注明来源

关键词: