共计 5795 个字符,预计需要花费 15 分钟才能阅读完成。
在 MongoDB 中,有两种方式计算聚合:Pipeline 和 MapReduce。Pipeline 查询速度快于 MapReduce,但是 MapReduce 的强大之处在于能够在多台 Server 上并行执行复杂的聚合逻辑。MongoDB 不允许 Pipeline 的单个聚合操作占用过多的系统内存,如果一个聚合操作消耗 20% 以上的内存,那么 MongoDB 直接停止操作,并向客户端输出错误消息。
一,使用 Pipeline 方式计算聚合
Pipeline 方式使用 db.collection.aggregate()函数进行聚合运算,运算速度较快,操作简单,但是,Pipeline 方式有两个限制:单个聚合操作消耗的内存不能超过 20%,聚合操作返回的结果集必须限制在 16MB 以内。
创建示例数据,在集合 foo 中插入 1000 条 doc,每个 doc 中有三个 field:idx,name 和 age。
for(i=0;i<10000;i++)
{db.foo.insert({"idx":i,name:"user"+i,age:i%90});}
1,使用 $match 管道符过滤 collection 中 doc,使符合条件的 doc 进入 pipeline,能够减少聚合操作消耗的内存,提高聚合的效率。
db.foo.aggregate({$match:{age:{$lte:25}}})
2,使用 $project 管道符,使用 doc 中的部分 field 进入下级 pipeline
db.foo.aggregate({$match:{age:{$lte:25}}},
{$project:{age:1,idx:1,"_id":0}}
)
$project 管道符的作用是选择字段,重命名字段,派生字段。
2.1 选择字段
在 $project 管道符中,field:1/0,表示选择 / 不选择 field;将无用的字段从 pipeline 中过滤掉,能够减少聚合操作对内存的消耗。
db.foo.aggregate({$match:{age:{$lte:25}}},
{$project:{age:1,idx:1,"_id":0}}
)
2.2 对字段重命名,产生新的字段
引用符 $,格式是:”$field”,表示引用 doc 中 field 的值,如果要引用内嵌 doc 中的字段,使用 “$field1.filed2″,表示引用内嵌文档 field1 中的字段:field2 的值。
示例,新建一个 field:preIdx,其值和 idx 字段的值是相同的。
db.foo.aggregate({$match:{age:{$lte:25}}},
{$project:{age:1,"preIdx":"$idx",idx:1,"_id":0}}
)
2.3 派生字段
在 $project 中,对字段进行计算,根据 doc 中的字段值和表达式,派生一个新的字段。
示例,preIdx 是根据当前 doc 的 idx 减 1 得到的
db.foo.aggregate({$match:{age:{$lte:25}}},
{$project:
{age:1,
"preIdx":{$subtract:["$idx",1]},
idx:1,
"_id":0}
}
)
在 $project 执行算术运算的操作符:+($add),*($multiply),/($divide),%($mod),-($subtract)。
对于字符数据,$substr:[expr,start,length]用于求子字符串;$concat:[expr1,expr2,,,exprn],用于将表达式连接在一起;$toLower:expr 和 $toUpper:expr 用于返回 expr 的小写或大写形式。
2.4 分组操作
使用 $group 将 doc 按照特定的字段的值进行分组,$group 将分组字段的值相同的 doc 作为一个分组进行聚合计算。如果没有 $group 管道符,那么所有 doc 作为一个分组。对每一个分组,都能根据业务逻辑需要计算特定的聚合值。分组操作和排序操作都是非流式的运算符,流式运算符是指:只要有新 doc 进入,就可以对 doc 进行处理,而非流式运算符是指:必须等收到所有的文档之后,才能对文档进行处理。分组运算符的处理方式是等接收到所有的 doc 之后,才能对 doc 进行分组,然后将各个分组发送给 pipeline 的下一个运算符进行处理。
示例,按照 age 进行分组,统计每个分组中的 doc 数量
db.foo.aggregate({$match:{age:{$lte:25}}},
{$project:{age:1,"preIdx":{$subtract:["$idx",1]},idx:1,"_id":0}} ,
{$group:{"_id":"$age",count:{$sum:1}}}
)
如果分组字段有多个,按照 age 和 age2 进行分组,这样做仅仅是为了演示,在实际的产品环境中,可以使用更多的字段用来分组。
db.foo.aggregate({$match:{age:{$lte:25}}},
{$project:{age:1,"preIdx":{$subtract:["$idx",1]},idx:1,"_id":0}} ,
{$group:{"_id":{age:"$age",age2:"$age"},count:{$sum:1}}}
)
对每个分组进行聚合运算,count 字段是计算每个分组中 doc 的数量,idxTotal 字段是计算每个分组中 idx 字段值的加和,idxMax 字段是计算每个分组中 idx 字段值的最大值,idxFirst 是计算每个分组中第一个 idx 字段的值,不一定是最小的。
db.foo.aggregate({$match:{age:{$lte:25}}},
{$project:{age:1,"preIdx":{$subtract:["$idx",1]},idx:1,"_id":0}} ,
{$group:
{"_id":{age:"$age",age2:"$age"},
count:{$sum:1},
idxTotal:{$sum:"$idx"}},
idxMax:{$max:"$idx"},
idxFirst:{$first:"$idx"}
}
}
)
2.5,sort 操作,limit 操作 和 skip 操作
对聚合操作的结果进行排序,然后跳过前 10 个 doc,取剩余结果集的前 10 个 doc。
db.foo.aggregate({$match:{age:{$lte:25}}},
{$project:{age:1,"preIdx":{$subtract:["$idx",1]},idx:1,"_id":0}} ,
{$group:
{"_id":{age:"$age",age2:"$age"},
count:{$sum:1},
idxTotal:{$sum:"$idx"}},
idxMax:{$max:"$idx"},
idxFirst:{$first:"$idx"}
}
},{$sort:{age:-1}},
{$skip:10},
{$limit:10}
)
二,使用 MapReduce 方式计算聚合
MapReduce 能够计算非常复杂的聚合逻辑,非常灵活,但是,MapReduce 非常慢,不应该用于实时的数据分析中。MapReduce 能够在多台 Server 上并行执行,每台 Server 只负责完成一部分 wordload,最后将 wordload 发送到 Master Server 上合并,计算出最终的结果集,返回客户端。
MapReduce 分为两个阶段:Map 和 Reduce,举个例子说明,有 10 节车厢,统计这 10 节车厢中男生和女生的数量。串行方式一节一节车厢的统计,直到统计完全部车厢中的人数:男 50 人,女 40 人。
使用 MapReduce 方式的思路是:每个车厢派一个人去统计,每个人返回一个 doc,例如,keyN:{female:num1,male:num2},keyN 是车厢编号,在同一时间,有 10 个人在同时工作,每个人只完成全部 workload 的 10%,很快,返回 10 个 doc,从 Key1 到 Key10,只需要将这 10 个 doc 中 femal 和 male 分别加和到一起,就是全部车厢的人数:男 50 人,女 40 人。
使用 MapReduce 方式计算聚合,主要分为三步:Map,Shuffle(拼凑)和 Reduce,Map 和 Reduce 需要显式定义,shuffle 由 MongoDB 来实现。
- Map:将操作映射到每个 doc,产生 Key 和 Value,例如,Map 一个 doc,产生(female,{count:1}),female 是 Key,value 是{count:1}
- Shuffle:按照 Key 进行分组,并将 key 相同的 Value 组合成数组,例如,产生(female,[{count:1},{count:1},{count:1},{count:1},,,,,])
- Reduce:把 Value 数组化简为单值,例如,产生(femal,{count:21})
使用 MapReduce 进行聚合运算的最佳方式是聚合运算的结果能够加到一起,例如,求最大值 / 最小值,sum,平均值(转换为计算每台 Server 的 总和 sum1,sum2,,,sumN 与 num1,num2,,numN,平均值 avg=(sum1+sum2+,,,+sumN)/(num1+num2+,,+numN))等。
示例,使用 MapReduce 模拟 Count,统计集合中的 doc 的数量
step1,定义 Map 函数和 reduce 函数
对于每个 doc,直接返回 key 和 一个 doc:{count:1}
map=function (){for(var key in this)
{emit(key,{count:1});
}
}
reduce=function (key,emits){total=0;
for(var i in emits){total+=emits[i].count;
}
return {"count":total};
}
step2,执行 MapReduce 运算
在集合 foo 上执行 MapReduce 运算,返回 mr 对象
mr=db.runCommand(
{"mapreduce":"foo",
"map":map,
"reduce":reduce,
out:"Count Doc"
})
step3,查看 MapReduce 计算的结果
db[mr.result].find()
示例 2,统计集合 foo 中不同 age 的数量
step1,定义 Map 和 Reduce 函数
Map 函数的作用是对每个 doc 进行一次映射,返回 age 和 {count:1};
经过 Shuffle,每个 age 都有一个列表:[{count:1},{count:1},{count:1},{count:1},,,,,],有多少个不同的 age,MongoDB 都会调用多少次 Reduce 函数,每次调用时,Key 值是不同的。
Reduce 函数的作用:对 MongoDB 的一次调用,对 age 对应的列表进行聚合运算。
map=function ()
{emit(this.age,{count:1});
}
reduce= function (key,emits)
{total=0;
for(var i in emits)
{total+=emits[i].count;
}
return {"age":key,count:total};
}
step2,执行 MapReduce 聚合运算
mr=db.runCommand(
{"mapreduce":"foo",
"map":map,
"reduce":reduce,
out:"Count Doc"
})
step3,查看聚合运算的结果
db[mr.result].find()
示例 3,研究 reduce 函数的特性
reduce 函数具有累加的特性,通过多次调用,能够产生最终的累加值,例如,以下 reduce 函数对于任意一个特定的 key,reduce 都能计算 key 的数量
reduce= function (key,emits)
{total=0;
for(var i in emits)
{total+=emits[i].count;
}
return {"key":key,count:total};
}
调用示例:传递的 Key 是相同的,都是“x”,每个 emits 都是一个数组,反复调用 reduce 函数,最终获得 key 的累加值。
r1=reduce("x",[{count:1},{count:2}])
r2=reduce("x",[{count:3},{count:5}])
r3=reduce("x",[r1,r2])
参考 doc:
Aggregation
更多 MongoDB 相关教程见以下内容:
CentOS 编译安装 MongoDB 与 mongoDB 的 php 扩展 http://www.linuxidc.com/Linux/2012-02/53833.htm
CentOS 6 使用 yum 安装 MongoDB 及服务器端配置 http://www.linuxidc.com/Linux/2012-08/68196.htm
Ubuntu 13.04 下安装 MongoDB2.4.3 http://www.linuxidc.com/Linux/2013-05/84227.htm
MongoDB 入门必读(概念与实战并重) http://www.linuxidc.com/Linux/2013-07/87105.htm
Ubunu 14.04 下 MongoDB 的安装指南 http://www.linuxidc.com/Linux/2014-08/105364.htm
《MongoDB 权威指南》(MongoDB: The Definitive Guide)英文文字版[PDF] http://www.linuxidc.com/Linux/2012-07/66735.htm
Nagios 监控 MongoDB 分片集群服务实战 http://www.linuxidc.com/Linux/2014-10/107826.htm
基于 CentOS 6.5 操作系统搭建 MongoDB 服务 http://www.linuxidc.com/Linux/2014-11/108900.htm
MongoDB 的详细介绍:请点这里
MongoDB 的下载地址:请点这里
本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-09/135277.htm