在功能点统计中,我是用mongodb所提供的mapreduce功能所实现的。关于mongodb,其实并没有多少可说的。简单的概述一下,它其实不过是一个可以存放很多json的仓库,只不过它对于json做了优化,使用二进制存储,改了个名叫做bson,这不过是很多年前就有的面向对象数据库的新的实现。至于性能,也可以简单的认为它自带了一个缓存,与硬盘做异步同步。这些其实都是很无趣的东西,真正有意思的,是它带了一个mapreduce工具(虽说,实现的真的很烂)。
mapreduce在我看来,是在互联网时代,一个程序员必须去了解的概念。在中文维基中,mapreduce被这样定义:“MapReduce是Google提出的一个软件架构,用于大规模数据集(大于1TB)的并行运算”。其实,这不准确,它其实是函数式语言中由来已久的一个概念。只不过,在这个互联网上信息爆炸的年代,google帮我们发现了它。
顾名思义,mapreduce分两部分,map和reduce。
map函数的定义为Map(k1,v1) → list(k2,v2)
reduce函数的定义Reduce(k2, list(v2)) → list(v3)
简单的描述一下,map接收一个list,通过某一个函数,把它变成另一个list。在这个过程中,对list中任一元素的变换都与其他操作无关。这样的函数天生具备了并行计算的能力。而reduce,它通过某种操作,具体来说也是通过一个函数,把接收到的list转变为一个简单的结果。reduce函数不是那么方便并行,所以通常mapreduce框架会把reduce操作集中到某个特定的节点进行。
看一下功能点统计中一段具体的mapreduce函数
var map =function () {
var a = new Date,
d = new Date(a.getFullYear(), a.getMonth(), a.getDate()),
e = this.auid;
this.features.forEach(function (b) {
var a = b.log.length,
c = 0;
b.log.forEach(function (a) {
a > d && c++
});
emit({
auid: e,
fid: b.id,
fname: b.name,
version: b.version
}, {
total: a,
today: c
})
})
}
在这段函数中,前面具体的逻辑操作不重要,重要的是后面的emit函数。这个函数由mongodb提供,在这个例子中,它将原始数据进行分割,分割结果是这样的结构
[{_id:{auid:xxx,fid:xxx,fname:xxx,version:xxx},value:[{total:xx,today:xxx}....]}]
换句话说,emit的作用就是根据第一个参数提供的属性,将原始数据分割为一个新的list
这里是reduce函数
var reduce = function (d, c) {
var a = {
total: 0,
today: 0
};
c.forEach(function (b) {
a.total += b.total;
a.today += b.today
});
return a
}
这个函数看起来就简单很多了,它要做的就是,接收上一个list中某个元素的value的值,这是一个小的list,把它变成一个对象输出。
从逻辑上可以看到,这个reduce对之前那个list的每个元素要执行一次,这个调度工作由mongodb执行。最终,这些输出结果合并到一起,变成新的list,成为我们的最终结果。
之前提到,我对mongodb的mapreduce其实非常不满。原因很多,首先它不能利用index,导致性能不高;其次,它对mapreduce所用到的数据有诸多限制;最重要的一点,其实是它根本没有很好的实现并行:在单机环境下,mongodb的mapreduce只能单线程运行,在分布式环境下有限制了很多有用的特性。这样看来,mongodb的mapreduce事实上只是起到了一个简单的group by作用,而不是一个真正最强有力的并行计算工具。在这里,我的观点是研究mapreduce的思想,远比研究mongodb的这个实现有价值的多。